Java 序列化详解
口语化的回答
之所以需要序列化,是为了解决网络通信之间对象传输的一个问题。也就是说怎么把当前JVM进程里面的一个对象,跨网络传输到另外一个JVM进程里进行恢复。
而序列化呢,就是把内存里面的对象转化为字节流,以便用来实现存储和传输,
而反序列化,就是根据从文件或者网络上获取到的对象的一个字节流,根据字节流里保存的对象描述信息和状态,重新构建一个新的对象。
其次啊,序列化的前提是为了保证通信双方对于对象的一个可识别性,所以很多时候我们会把对象转换为通用的解析格式,比如说像json、xml或者是其他的一些格式。然后再把他们转化为数据流进行网络传输,从而去实现跨平台或者跨语言的这样一个可识别性。
最后我再补充一下序列化的一个选择问题啊,市面上开源的序列化技术非常多,比如说想json、xml、protobuf、kyro、hessian等等,在实际应用里面那种序列化更合适,我认为有几个关键因素。
- 序列化之后的数据大小,因为数据大小会影响传输性能
- 序列化的性能,序列化耗时较长会影响业务性能
- 是否支持跨平台、跨语言
- 技术的成熟度,越成熟的方案使用的公司越多,也就越稳定
什么是序列化?什么是反序列化?
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
简单来说:
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
下面是序列化和反序列化常见应用场景:
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,使用 transient
关键字修饰。
transient
关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient
修饰的变量值不会被持久化和恢复。
关于 transient
还有几点注意:
transient
只能修饰变量,不能修饰类和方法。transient
修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰int
类型,那么反序列后结果就是0
。static
变量因为不属于任何对象(Object),所以无论有没有transient
关键字修饰,均不会被序列化
常见序列化协议有哪些?
比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择
JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。
为什么不推荐使用 JDK 自带的序列化?
我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:
-
不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
-
性能差:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
-
存在安全问题:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么Attackers即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。