Java序列化就是将一个对象转化为一个二进制表示的字节数组,通过保存或则转移这些二进制数组达到持久化的目的。要实现序列化,需要实现java.io.Serializable接口。反序列化是和序列化相反的过程,就是把二进制数组转化为对象的过程。在反序列化的时候,必须有原始类的模板才能将对象还原。从这个过程我们可以猜测到,序列化过程并不想class文件那样保存类的完整的结构信息。下面我们以一个简单的例子来看一下,序列化的时候都保存了哪些信息。代码如下:
package com.ysl;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializableTest implements Serializable{
private static final long serialVersionUID = -1L;
public int num = 2018;
public static void main(String[] args){
try {
FileOutputStream fos = new FileOutputStream("serializable");
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerializableTest test = new SerializableTest();
oos.writeObject(test);
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
序列化后的二进制字节数据如下:
aced 0005 7372 0018 636f 6d2e 7973 6c2e
5365 7269 616c 697a 6162 6c65 5465 7374
ffff ffff ffff ffff 0200 0149 0003 6e75
6d78 7000 0007 e2
上述的内容分为一下几个部分:
第一部分是序列化文件头
- AC ED :STREAM_MAGIC声明使用了序列化协议
- 00 05 :STREAM_VERSION序列化协议版本
- 73 :TC_OBJECT声明这是一个新的对象
第二部分是序列化的类的描述,在这里是SerializableTest
- 72 :TC_CLASSDESC声明这里开始一个新的class
- 00 18:class名字的长度是24个字节
- 636f 6d2e 7973 6c2e 5365 7269 616c 697a 6162 6c65 5465 7374:SerializableTest的完整类名
- ffff ffff ffff ffff:serialVersionUID,序列化ID,如果没有指定,则会由算法随机生成一个8字节的ID
- 02 :标记号,声明该类支持序列化
- 00 01:该类所包含的域的个数为1
第三部分是对象中各个属性的描述
- 49:域类型,49代表I,也就是int类型
- 00 03:域名字的长度为3
- 6e 75 6d:num属性的名称
第四部分为对象的父类信息描述
SerializableTest没有父类,如果有,和第二部分的描述相同
- 78 :TC_ENDBLOCKDATA,对象块的结束标志
- 70:TC_NUL:说明没有其他超类的标志
第五部分为对象属性的实际值
如果属性是一个对象,那么这里还将序列化这个对象,规则和第二部分一样
- 00 0007 e2:数值2018
虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,下面是对一些复杂情况的总结:
- 当父类实现了Serializable接口的时候,所有的子类都能序列化
- 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,但是数据会丢失)
- 如果序列化的属性是对象,对象必须也能序列化,否则会报错
- 反序列化的时候,如果对象的属性有修改或则删减,修改的部分属性会丢失,但是不会报错
- 在反序列化的时候serialVersionUID被修改的话,会反序列化失败
- 在存Java环境下使用Java的序列化机制会支持的很好,但是在多语言环境下需要考虑别的序列化机制,比如xml,json,或则protobuf