一、什么是序列化和反序列化
- 序列化:将对象的状态信息转换成可以存储或者传输的二进制格式的过程。
- 反序列化:将二进制存储形式转换成对象的过程。
二、为什么需要序列化和反序列化
对象序列化是指对于实现了Serializable接口的对象,可将它们转换为一系列字节,在需要的时候可以将它们复原,这些字节可以在网络中传输,意味着序列化可以弥补不同操作系统之间的差异,我们平时用到的RPC框架就需要将对象序列化后在传输,如果系统之间序列化出问题,将会影响系统服务的可靠性。
三、序列化/反序列化
1、序列化/反序列化的实现方式
首先创建一个FileOutputStream,然后将其封装到ObjectOutputStrem对象内,在调用writeObject()即可完成对象的序列化。
在恢复对象的时候,首先创建一个FileInputStream,然后将其封装在ObjectInputStream对象内,在调用readObect()即可对对象进行反序列化。
序列化运行时使用一个称为serialVersionUID的版本好与每个可序列化类相关联,在实现Serializable接口后,生成一串版本号,在反序列化过程终用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类,如果两个对象的serialVersionUID与对应的发送者的类的版本号不同,则反序列化将会抛出InvalidClassException异常。
- 继承了Serializable的对象
//代码示例
public class Data implements Serializable {
private final long serialVersionUID = 1L;
private int i;
Data(int x){
this.i = x;
}
@Override
public String toString(){
return Integer.toString(i);
}
}
- 序列化对象
public static void main(String[] args) {
Data data = new Data(1);
try {
FileOutputStream fileOutputStream = new FileOutputStream("data.out");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(data);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
序列化后的对象:
- 还原对象
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream("data.out");
ObjectInputStream in = new ObjectInputStream(fileInputStream);
Data data = (Data) in.readObject();
System.out.println(" data = " + data);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
2、transient,可选的自定义序列化
使用transient修饰的属性,java序列化时,会忽略掉此字段,所以反序列化出的对象,被transient修饰的属性是默认值。对于引用类型,值是null;基本类型,值是0;boolean类型,值是false。
使用transient虽然简单,但将此属性完全隔离在了序列化之外,Java提供了可选的自定义序列化,可以进行控制序列化的方式,或者对序列化数据进行编码加密等。
3、Externalizable:强制自定义序列化
Externalizable接口不同于Serializable接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的,并且必须提供public的无参构造器,在反序列化的时候需要通过反射创建对象。
- java.io中的Externalizable接口
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
- Person实体
public class Person implements Externalizable {
private String name;
private int age;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
//out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = in.readObject().toString();
//this.age = in.readInt();
}
}
- Person的序列化和反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
oos.writeObject(new Person("whiltes", 23));
Person person = (Person)ois.readObject();
System.out.println(person);
}
4、Serializable接口和Externalizable接口对比
实现Serializable接口 | 实现Externalizable接口 |
---|---|
系统自动存储必要的信息 | 程序员决定存储哪些信息 |
Java内建支持,易于实现,只需要实现该接口即可,无需代码支持 | 必须实现接口内的方法 |
性能略差 | 性能略好 |
Externalizable接口带来了一定的性能提升,但复杂度也提高了,一般通过实现Serializable接口进行序列化
四、序列化的一些特性
- 利用序列化可以实现“有限持久化”,“持久化”意味着“生存时间”并不取决于册灰姑娘徐是否正在执行,通过序列化一个对象,将其写入磁盘,以后在程序重新调用时重新恢复那个对象,就能实现一种“持久”效果。
- 有了序列化的特性后,可以实现“远程方法调用(RMI)”
- 对一个可序列化对象进行重新装配时,不会调用任何构造器(甚至默认构造器)。整个对象都是通过从IntputStream中取得数据恢复的。
五、总结
- 所有需要网络传输的对象都需要实现序列化接口
- transient修饰的示例变量不会被序列化
- 序列化对象的引用类型成员变量,也需要是可序列化的
- 反序列化时必须有序列化对象的class文件
- 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化
- 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级
六、补充
在我们平时使用时,特别是rpc框架的使用,相关类经常会有新增方法或者新增字段和删减字段,序列化会进行一个匹配操作,尽量的还原到当前版本。
如果是新增了方法,不会有什么影响,如果该类新增了字段或者删减字段,也不会有影响,当某个字段的类型有变更时,在反序列化是就会出问题。
新增字段的时候,原始版本中没有这个字段,会默认赋空值。如果是删减字段,在反序列化时,会赋予默认值(如果是对象则是null,如果是数字则为0,如果是boolean值则是false)