一、简介
定义:
序列化:序列化是将对象转换为字节流。
反序列化:范序列化是将字节流转换为对象。
序列化的作用有:
序列化可以将对象的字节序列持久化保存在内存、文件、数据库中。
在网络上传送对象的字节序列。
RMI(远程方法调用)
二、序列化和反序列化
Java通过对象输入输出流来实现序列化和反序列化:
序列化:java.io.ObjectOutputStream类的writeObject()方法可以实现序列化。
反序列化:java.io.ObjectInputStream类的readObject()方法用于实现反序列化。
package com.zhanzhuang.test01; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; public class SerializeDemo01 { enum Sex { MALE, FEMALE } static class Person implements Serializable { private static final long serialBersionUID = 1L; private String name = null; private Integer age = null; private Sex sex; public Person() { System.out.println("call Person()"); } public Person(String name, Integer age, Sex sex) { this.name = name; this.age = age; this.sex = sex; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } } /** * 序列化 */ private static void serialize(String filename) throws IOException { File f = new File(filename); // 定义保存路径 OutputStream out = new FileOutputStream(f); // 文件输出流 ObjectOutputStream oos = new ObjectOutputStream(out); // 对象输出流 oos.writeObject(new Person("Jack", 30, Sex.MALE)); // 保存对象 oos.close(); out.close(); } /** * 反序列化 */ private static void deserialize(String filename) throws IOException, ClassNotFoundException { File f = new File(filename); // 定义保存路径 InputStream in = new FileInputStream(f); // 文件输入流 ObjectInputStream ois = new ObjectInputStream(in); // 对象输入流 Object obj = ois.readObject(); // 读取对象 ois.close(); in.close(); System.out.println(obj); } public static void main(String[] args) throws IOException, ClassNotFoundException { final String filename = "d:/text.dat"; serialize(filename); deserialize(filename); // File f = new File("d:/zhan.txt"); } }
输出:Person [name=Jack, age=30, sex=MALE]
三、Serializable接口
被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
如果不是Enum、Array的类、如果需要序列化、必须实现java.io.Serializable接口,否则将抛出NotSerializableException异常。这是因为:在序列化操作过程中会对类型进行检查,如果不满足序列化类型要求,就会抛出异常。
下面做一个小尝试,将SerializeDemo01.java中的内部类Person改为如下实现,继续执行查看运行结果。
果然不出所料,抛出了NotSerializableException异常。
四、serialVersionUID
serialVersionUID这个字段,我们在Java世界中的无数个类中看到这个字段。
serialVersionUID有什么作用,如何使用serialVersionUID?
它是Java为每个序列化类产生的版本标识。它可以用来保证在反序列化时,发送方发送和接收方接受的是可兼容的对象。
如果接收方接收的类的serialVersionUID与发送方发送的serialVersionUID不一致,会抛出InvalidClassException。
注意:如果可序列化类没有声明serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认serialVersionUID值。尽管这样,还是建议在每一个序列化的类中声名serialVersionUID的值。
因为不同的jdk编译很可能会生成不同的serialVersionUID默认值,从而导致在反序列化时抛出InvalidClassException异常。
serialVersionUID字段必须是static final long类型的。
我们来举一个例子:
①在版本信息为1L的时候进行序列化后执行反序列化(序列化为1L,反序列化为1L。我猜不会报错):
执行后输出:Person [name=Jack, age=30, sex=MALE]。
②在开发过程中修改了内容,所以要修改序列化的版本信息,但是没有进行序列化,直接进行反序列化(序列化为1L,反序列化为2L。我猜会报错):
会抛出异常:
由于在②步骤中修改了内容,导致与版本不兼容,所以要修改序列化版本号
综上所述:
我们大概可以清楚:serialVersionUID 用于控制序列化版本是否兼容。若我们认为修改的可序列化类是向后兼容的,则不修改 serialVersionUID。
五、默认序列化机制
如果仅仅只是让某个类实现Serializable接口,而没有其他任何处理的话,那么就是使用默认序列化机制。
使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对其父类的字段以及该对象引用的其他对象也进行序列化。同样的,这些其他对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会比较复杂,开销也比较大。
注意:这里的父类和引用对象既然要进行序列化,那么他们当然也要满足序列化要求:被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
六、非默认序列化机制
在现实应用中,有些时候不能用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
transient关键字
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。
我们将SerializeDemo01示例中的内部类Person的age字段声明为transient,如下所示:
public class SerializeDemo02 { static class Person implements Serializable { transient private Integer age = null; // 其他内容略 } // 其他内容略 }
输出:Person [name=Jack, age=null, sex=MALE]
从输出的结果可以看出,age字段没有被序列化。