Java序列化详解

目录

一、什么是序列化

二、什么是反序列化

三、序列化和反序列化的作用

四、序列化和反序列化应用案例

五、常见序列化协议对比

5.1 JDK 自带的序列化方式

5.2 JDK序列化的缺陷

1. 无法跨语言

2. 易被攻击

3. 序列化后的流太大

4. 序列化性能太差

5.3 Kryo

5.4 Protobuf

5.5 总结


一、什么是序列化

序列化是指将对象转化为字节流的过程,以便于存储或传输。在序列化过程中,对象的状态被保存为一连串的字节,可以将这些字节保存到文件中或通过网络传输。序列化后的字节流可以在需要时进行反序列化,将字节流重新转化为对象,并恢复对象的状态。

在Java中,对象的序列化是通过实现Serializable接口来实现的。Serializable接口是一个标记接口,没有任何方法,只是用于标识一个类可以被序列化。当一个类实现了Serializable接口,它的对象就可以被序列化为字节流。

序列化在很多场景中都有应用,例如在分布式系统中,可以将对象序列化后通过网络传输,或者将对象存储到缓存中。但需要注意的是,序列化和反序列化可能会引发安全问题,因此在进行序列化和反序列化操作时,要谨慎处理。

二、什么是反序列化

反序列化是指将字节流转化为对象的过程,与序列化相反。在反序列化过程中,字节流被重新组装成对象,并恢复对象的状态。在JDK中,反序列化是通过ObjectInputStream类来实现的。ObjectInputStream类提供了readObject()方法,用于从输入流中读取对象。

反序列化是序列化的逆过程,可以将序列化后的字节流重新转化为原始的对象。这在很多场景中都有应用,例如在分布式系统中,可以将序列化后的对象通过网络传输,然后在接收方进行反序列化,恢复原始的对象。需要注意的是,在进行反序列化操作时,需要确保序列化和反序列化的版本一致,否则可能会出现不兼容的问题。

三、序列化和反序列化的作用

序列化和反序列化是用于在Java中将对象转换为字节流并从字节流中恢复对象的过程。它们的作用主要有以下几个方面:

数据持久化:通过序列化,可以将对象转换为字节流并保存到文件系统或数据库中。这样可以实现数据的持久化存储,方便后续的读取和使用。

对象传输:通过序列化,可以将对象转换为字节流,并在网络中传输。这在分布式系统、远程方法调用等场景中非常常见。发送方将对象序列化为字节流,通过网络发送给接收方,接收方再通过反序列化将字节流转换为对象。

缓存机制:序列化可以用于缓存机制,将对象序列化后存储在缓存中,以提高系统性能和响应速度。当需要使用对象时,可以直接从缓存中反序列化获取对象,避免了频繁的数据库访问或计算操作。

跨平台和跨语言通信:通过序列化,可以将对象转换为字节流,使得对象在不同的平台和不同的编程语言之间进行通信成为可能。只要各方都能正确地进行序列化和反序列化操作,就可以实现跨平台和跨语言的通信。

序列化和反序列化提供了一种方便的方式来将对象转换为字节流,并在需要时恢复为原始对象。它们在数据持久化、对象传输、缓存机制以及跨平台和跨语言通信等方面都有广泛的应用。

四、序列化和反序列化应用案例

序列化和反序列化在实际应用中有很多用途,以下是一些常见的应用案例:

数据库缓存:在Web应用中,为了提高性能,我们通常会将一些经常使用的数据缓存到内存中,以减少数据库的访问次数。这时,我们可以将数据对象序列化为字节流,存储到缓存中。当需要使用数据时,我们可以从缓存中读取字节流,并反序列化为对象,以提高访问速度。

分布式系统通信:在分布式系统中,不同的节点之间需要进行通信,传递数据对象是非常常见的操作。这时,我们可以将数据对象序列化为字节流,并通过网络传递。接收方再将字节流反序列化为对象,以获取数据。

消息队列:消息队列是一种常用的异步通信方式,可以将消息对象序列化为字节流,放入消息队列中。消费者再从消息队列中获取字节流,并反序列化为对象,以获取消息内容。

远程方法调用:在分布式系统中,我们通常需要调用远程节点的方法。这时,我们可以将方法参数对象序列化为字节流,通过网络传递给远程节点,并在远程节点上反序列化为对象,以调用方法。

对象持久化:在一些应用中,我们需要将数据对象持久化到磁盘中,以便下次启动应用时能够恢复数据。这时,我们可以将数据对象序列化为字节流,存储到磁盘中。下次启动应用时,我们可以从磁盘中读取字节流,并反序列化为对象,以恢复数据。

序列化和反序列化在很多应用场景中都有广泛的应用,可以方便地将对象转换为字节流,并在需要时恢复为原始对象。

五、常见序列化协议对比

​ JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且部分版本有安全漏洞。(为啥效率低?安全漏洞又是啥?)比较常用的序列化协议有 hessian、kyro、protostuff。

​ 下面提到的都是基于二进制的序列化协议,像 JSON 和 XML 这种属于文本类序列化方式。虽然 JSON 和 XML 可读性比较好,但是性能较差,一般不会选择。

5.1 JDK 自带的序列化方式

​ JDK 自带的序列化,只需实现 java.io.Serializable接口即可。比如下面的Student类:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
@ToString
public class Student implements Serializable {private static final long serialVersionUID = 1905122041950251207L;private String name;private transient Integer age;private String address;
}

private transient Integer age中的transient表示不会序列化该属性,当对象被序列化时该属性age不会被序列化,反序列化时,该属性是以默认值赋值。比如下面反序列化时,age的值为空。

public class SerializeOperation {/*** 序列化对象,并使用了try-with-resources* @param student* @param path* @throws IOException*/public static void serializeToFile(Student student, String path) throws IOException {Student s = new Student("小明",16,"翻斗花园");try(FileOutputStream fileOut = new FileOutputStream(path);ObjectOutputStream out = new ObjectOutputStream(fileOut);) {out.writeObject(s);}catch (Exception e){e.printStackTrace();}}/*** 反序列化对象* @param path*/public static void deserializationFromFile(String path){try(FileInputStream fileIn = new FileInputStream(path);ObjectInputStream in = new ObjectInputStream(fileIn);){Student s = (Student) in.readObject();System.out.println(s.getAddress());System.out.println(s.getAge());System.out.println(s.getName());}catch (Exception e){e.printStackTrace();}}
}

上面使用了try-with-resources可以自动关闭任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象。这样我们就不用在finally再关闭了。

主程序:

public class mainPractice {public static void main(String[] args) throws IOException {Student toBeSerializedObject = new Student("小明",16,"翻斗花园!");StringBuffer path = new StringBuffer("E:");path.append(File.separator).append("test").append(File.separator).append("student.ser");SerializeOperation.serializeToFile(toBeSerializedObject, path.toString());SerializeOperation.deserializationFromFile(path.toString());}
}

5.2 JDK序列化的缺陷

​ 我们在用过的RPC(远程方法调用)通信框架中,很少会发现使用JDK提供的序列化,主要是因为JDK默认的序列化存在着如下一些缺陷:

1. 无法跨语言

​ 现在很多系统的复杂度很高,采用多种语言来编码,而Java序列化目前只支持Java语言实现的框架,其它语言大部分都没有使用Java的序列化框架,也没有实现Java序列化这套协议,因此,如果两个基于不同语言编写的应用程序之间通信,使用Java序列化,则无法实现两个应用服务之间传输对象的序列化和反序列化。 像JSON序列化的话就可以跨语言,因为JSON这种数据格式是通用的。

2. 易被攻击

​ Java官网安全编码指导方针里有说明,“对于不信任数据的反序列化,从本质上来说是危险的,应该避免“。可见Java序列化并不是安全的。

​ 我们知道对象是通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的,这个方法其实是一个神奇的构造器,它可以将类路径上几乎所有实现了 Serializable 接口的对象都实例化。这也就意味着,在反序列化字节流的过程中,该方法可以执行任意类型的代码,这是非常危险的

​ 对于需要长时间进行反序列化的对象,不需要执行任何代码,也可以发起一次攻击。攻击者可以创建循环对象链,然后将序列化后的对象传输到程序中反序列化,这种情况会导致 hashCode 方法被调用次数呈次方爆发式增长, 从而引发栈溢出异常。例如下面这个案例就可以很好地说明。

Set root = new HashSet();  
Set s1 = root;  
Set s2 = new HashSet();  
for (int i = 0; i < 100; i++) {  Set t1 = new HashSet();  Set t2 = new HashSet();  t1.add("test"); //使t2不等于t1  s1.add(t1);  s1.add(t2);  s2.add(t1);  s2.add(t2);  s1 = t1;  s2 = t2;   
} 

如何解决这个漏洞?

​ 很多序列化协议都制定了一套数据结构来保存和获取对象。例如,JSON 序列化、ProtocolBuf 等,它们只支持一些基本类型和数组数据类型,这样可以避免反序列化创建一些不确定的实例。虽然它们的设计简单,但足以满足当前大部分系统的数据传输需求。我们也可以通过反序列化对象白名单来控制反序列化对象,可以重写 resolveClass 方法,并在该方法中校验对象名字。代码如下所示:

@Override
protected Class resolveClass(ObjectStreamClass desc) throws IOException,ClassNotFoundException {if (!desc.getName().equals(Bicycle.class.getName())) {throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());}return super.resolveClass(desc);
}
3. 序列化后的流太大

​ 序列化后的二进制流大小能体现序列化的性能。序列化后的二进制数组越大,占用的存储空间就越多,存储硬件的成本就越高。如果我们是进行网络传输,则占用的带宽就更多,这时就会影响到系统的吞吐量。

​ Java 序列化中使用了 ObjectOutputStream 来实现对象转二进制编码,那么这种序列化机制实现的二进制编码完成的二进制数组大小,相比于 NIO 中的 ByteBuffer 实现的二进制编码完成的数组大小,要大上几倍。

4. 序列化性能太差

​ Java 序列化中的编码耗时要比 ByteBuffer 长很多

5.3 Kryo

​ Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。

​ 另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。刚刚序列化和反序列化Student的案例在Kryo上使用如下:

public class KryoSerializerOperation {public static void serializeToFile(Object toBeSerializedObject, String path){Kryo kryo = new Kryo();kryo.register(toBeSerializedObject.getClass());try (Output output = new Output(new FileOutputStream(path));){kryo.writeObject(output, toBeSerializedObject);}catch (Exception e){e.printStackTrace();}}public static void deSerializeFromFile(Class toBeSerializedObject, String path){Kryo kryo = new Kryo();kryo.register(toBeSerializedObject);try (Input input = new Input(new FileInputStream(path));){Student s = (Student) kryo.readObject(input, toBeSerializedObject);System.out.println(s);}catch (Exception e){e.printStackTrace();}}
}

主程序:

public class mainPractice {public static void main(String[] args) {KryoSerializerOperation.serializeToFile(ConstantUsedBySerialization.student,ConstantUsedBySerialization.path);KryoSerializerOperation.deSerializeFromFile(ConstantUsedBySerialization.student.getClass(),ConstantUsedBySerialization.path);}
}

其中的变量:

public class ConstantUsedBySerialization {public static Student student = new Student("小明",16,"翻斗花园");public static String path = "E:"+ File.separator+"test"+File.separator+"student.ser";
}

5.4 Protobuf

​ Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不然灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。

Protobuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下你需要定义 proto 文件,然后使用 IDL 编译器编译成你需要的语言

​ 一个简单的 proto 文件如下:

// protobuf的版本
syntax = "proto3";
// SearchRequest会被编译成不同的编程语言的相应对象,比如Java中的class、Go中的struct
message Person {//string类型字段string name = 1;// int 类型字段int32 age = 2;
}

5.5 总结

​ Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/674666.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P8772 [蓝桥杯 2022 省 A] 求和--2024蓝桥杯冲刺省一

点击跳转例题 思路&#xff1a;简单数乘法结合律&#xff0c;然后前缀和的模板题&#xff1a; 前缀和的知识&#xff1a; 本题的代码&#xff1a;前缀和知识--模板&#xff1b; #include <bits/stdc.h> #define int long long //(有超时风险) #define PII pair<in…

rust语言tokio库底层原理解析

目录 1 rust版本及tokio版本说明1 tokio简介2 tokio::main2.1 tokio::main使用多线程模式2.2 tokio::main使用单线程模式 3 builder.build()函数3.1 build_threaded_runtime()函数新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图…

输入:123 输出321

给出一个 32 位的有符号整数&#xff0c;你需要将这个整数中每位上的数字进行反转。若翻转后超出取值范围&#xff0c;则输出0即可输入&#xff1a;123 输出321输入&#xff1a;-123 输出-321 #include <stdio.h> int main(int argc, char const *argv[]) { printf("…

StringBuilder与线程安全之StringBuffer

StringBuilder是Java中的一个类&#xff0c;位于java.lang包下&#xff0c;主要用于在程序中动态构建字符串。与String类相比&#xff0c;StringBuilder提供了一个可变的字符串序列&#xff0c;这意味着你可以在不生成新对象的情况下修改字符串的内容&#xff0c;从而提高了字符…

国产三维剖面仪—MPAS-100相控参量阵浅地层剖面仪

最近声学所东海站邹博士发来了他们最新的浅地层剖面仪—MPAS-100相控参量阵浅地层剖面仪的资料&#xff0c;市场型号GeoInsight&#xff0c;委托Ocean Physics Technology公司销售&#xff0c;地大李师兄的公司负责技术支持。 MPAS-100相控参量阵浅地层剖面仪就是俗称的三维浅…

git安装配置

1、下载安装 下载地址 2、配置git用户 git config --global user.name "yw" git config --global user.email "88888qq.com" 3、git init 初始化 4、生成ssh密钥 mkdir .ssh //创建文件夹cd .ssh //进入新建文件夹 ssh-keygen -t rsa // 输入密钥文…

(已解决)什么是vue导航守卫

vue导航守卫是是一种Vue Router内置的功能&#xff0c;它可以让我们在路由切换的过程中执行自定义的代码逻辑。 举一个简单的例子&#xff1a; import Vue from vue; import Router from vue-router;Vue.use(Router);const router new Router({// 路由配置... });// 全局前置…

SQL 注入 - http头注入之UA头注入探测

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 一、http头注入介绍 HTTP头注入是一种网络安全攻击手段,它利用了Web应用程序对HTTP头的处理不当或缺乏充分的验证和过滤。在这种攻击中,攻击者通过修改HTTP请求头中的某些字段,…

2、卷积和ReLU激活函数

python了解集合网络如何创建具有卷积层的特性。 文章目录 简介特征提取(Feature Extraction)卷积过滤(Filter with Convolution)Weights(权重)激活(Activations)用ReLU检测示例 - 应用卷积和ReLU结论In [1]: import numpy as np from itertools import productdef show_kerne…

Uniapp真机调试:手机端访问电脑端的后端接口解决

Uniapp真机调试&#xff1a;手机端访问电脑端的后端接口解决 1、前置操作 HBuilderX -> 运行 -> 运行到手机或模拟器 -> 运行到Android App基座 少了什么根据提示点击下载即可 使用数据线连接手机和电脑 手机端&#xff1a;打开开发者模式 -> USB调试打开手机端&…

使用 WMI 查询安全软件信息

在这篇文章中&#xff0c;我们将详细介绍如何使用 Windows Management Instrumentation (WMI) API 来查询当前计算机上安装的安全软件的基本信息。我们将分析代码的各个部分&#xff0c;并解释每个步骤所涉及的技术和原理。 一、什么是 WMI&#xff1f; WMI 是 Windows Manag…

Vue安装与配置

写入借鉴网址&#xff1a;好细的Vue安装与配置_vue配置-CSDN博客 下载Vue安装地址&#xff1a; Node.js — Download 查看是否安装成功&#xff1a; node -v npm -v 配置全局模式及缓存 结果通过&#xff1a; C:\Windows\system32>npm install vue -g added 20 packages …

大学生活的“三角平衡”与“合法”偷懒艺术

在那个被称为大学的神奇乐园里&#xff0c;我终于找到了自我&#xff0c;或者说&#xff0c;我找到了一种平衡。这种平衡被我称为“三角平衡”&#xff0c;它是由懒觉、兴趣爱好和学习这三者构成的。在这个平衡中&#xff0c;我像一名杂技演员一样&#xff0c;稳稳地站在三个顶…

C++重新入门-C++ 常量

目录 1.简介 2.整数常量 3.浮点常量 4.布尔常量 5.字符常量 6.字符串常量 7.如何定义常量 7.1 #define 预处理器 7.2const 关键字 1.简介 C 常量 常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。 常量可以是任何的基本数据…

jsp商场会员卡管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 商场会员卡管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.…

python中sort()函数的详细使用方法

目录 使用reverse指定排序顺序 使用key指定排序规则 使用匿名函数制定规则 定义比较函数制定规则 制定多规则 多复杂规则排序 sort()是python非常好用的排序函数&#xff0c;可以对一个列表进行排序&#xff0c;这个排序只是会修改原列表&#xff0c;不会创建新的列表 使…

QT学习(五)C++函数重载

一、 函数重载 在同一个作用域内&#xff0c;可以声明几个功能类似的同名函数&#xff0c; 这些同名函数的形式参数&#xff08;指参数的个数、类型或者顺序&#xff09;必须不同。您不能仅通过返回类型的不同来 重载函数。 下面的实例中&#xff0c;同名函数 print() 被用…

前端开发:(三)CSS入门

1. 介绍CSS 1.1 什么是CSS CSS&#xff08;Cascading Style Sheets&#xff09;是一种用于描述文档样式和布局的样式表语言&#xff0c;用于美化和排版HTML和XML等标记语言的内容。 1.2 CSS的作用和优势 CSS的主要作用是控制网页的样式和布局&#xff0c;包括字体、颜色、间…

Hive与Presto中的列转行区别

Hive与Presto列转行的区别 1、背景描述2、Hive/Spark列转行3、Presto列转行 1、背景描述 在处理数据时&#xff0c;我们经常会遇到一个字段存储多个值&#xff0c;这时需要把一行数据转换为多行数据&#xff0c;形成标准的结构化数据 例如&#xff0c;将下面的两列数据并列转换…

2024年 复习 HTML5+CSS3+移动web 笔记 之CSS遍 第6天

6.1 定位-相对和绝对和固定 6.2 相对和绝对和固定 6.3 堆叠顺序z-index 6.4 定位总结 6.5 CSS精灵 基本使用 6.6 案例 CSS精灵 京东服务 6.7 字体图标-下载和使用 6.8 字体图标-上传 6.9 垂直对齐方式vertical-align 6.10 过渡属性 6.11 修饰属性-透明度与光标类型 6.12 综合案…