Introduction
序列化其实可以看成是一种机制,按照一定的格式将Java对象的某状态转成介质可接受的形式,以方便存储或传输。
序列化时将Java对象相关的类信息、属性及属性值等等保存起来,反序列化时再根据这些信息构建出Java对象。而过程可能涉及到其他对象的引用,所以这里引用的对象的相关信息也要参与序列化。
将Java对象序列化为二进制文件的Java序列化技术是Java系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现Serializable接口,使用ObjectInputStream和ObjectOutputStream进行对象的读写。
序列化的作用:
- 提供一种简单又可扩展的对象保存恢复机制。
- 对于远程调用,能方便对对象进行编码和解码,就像实现对象直接传输。
- 可以将对象持久化到介质中,实现对象直接存储。
- 允许对象自定义外部存储的格式。
Attention
- 序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量
- SuperClass不实现Serializable接口,则不会保存SuperClass的状态变量
- 通过重写writeObject和readObject实现对敏感数据的加密和解密
- Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后transient变量的值被设为初始值,如int型的是0,对象型的是 null。
SuerperClass Serializer
如果一个子类实现了Serializable接口而父类没有实现该接口,则在序列化子类时,子类的属性状态会被写入而父类的属性状态将不被写入。所以如果想要父类属性状态也一起参与序列化,就要让它也实现 Serializable 接口。
如果父类未实现Serializable接口,则反序列化生成的对象会再次调用父类的构造函数,以此完成对父类的初始化。所以父类属性初始值一般都是类型的默认值。
Externalizable 接口作用
Externalizable接口主要就是提供给用户自己控制序列化内容,虽然transient和ObjectStreamField能定义序列化的字段,但通过Externalizable接口则能更加灵活。
它其实继承了Serializable 接口,提供了writeExternal和readExternal两个方法,也就是在这两个方法内控制序列化和反序列化的内容。
1 | public class ExternalizableTest implements Externalizable { |
serialVersionUID
在序列化操作时,经常会看到实现了Serializable 接口的类会存在一个serialVersionUID属性,并且它是一个固定数值的静态变量。
这个属性有什么作用?其实它主要用于验证版本一致性,每个类都拥有这么一个ID,在序列化的时候会一起被写入流中,那么在反序列化的时候就被拿出来跟当前类的serialVersionUID 值进行比较,两者相同则说明版本一致,可以序列化成功,而如果不同则序列化失败。
Example 1
Case: 两个客户端A和B试图通过网络传递对象数据,A端将对象C序列化为二进制数据再传给B,B反序列化得到C。C对象的全类路径假设为com.abc.model,在A和B端都有这么一个类文件,功能代码完全一致。也都实现了Serializable接口,但是反序列化时总是提示不成功。
关键点:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还有一个非常重要的点是两个类的序列化ID是否一致(serialVersionUID)。
序列化存储规则
Example 1
1 | import java.io.* |
输出结果
1 | 42 |
误区:两次写入对象,文件大小会变为两倍的大小,反序列化时,由于从文件读取,生成了两个对象,判断相等时应该是输入false才对。
Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的5字节的存储空间就是新增引用和一些控制信息的空间。
反序列化时,恢复引用关系,使得代码中的t1和t2指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。
Example 2
1 | import java.io.* |
输出
1 | 1 |
误区:写入一次以后修改对象属性值再次保存第二次,然后从result.obj中再依次读出两个对象,输出这两个对象的i属性值,因为保存的状态不一样,因此两个对象的i属性应该不同。
第一次写入对象以后,第二次再试图写的时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。因此在使用一个文件多次writeObject需要特别注意这个问题。
Reference
https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html
https://juejin.im/post/5a7111535188257350518592