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可以通过构建恶意输入,使反序列化产生意想不到的对象,并在此过程中执行任何构建代码。