关于对象流:ObjectInputStream和ObjectOutputStream
1、先来了解一下序列化与反序列化的概念:
序列化:将内存中的java对象分解成数小块,并按照顺序一块一块存储到硬盘文件中,成为对象的序列化。
反序列化:将硬盘中的对象文件,按照顺序还原到内存中,称为对象的反序列化。
2、对象能够序列化,必须实现可序列接口Serializable。
这个Serializable接口中没有任何代码,只是起到一个标志的作用。
java中的接口分两种:普通接口 和 标志接口,期中标志接口中没有任何代码。
Serializable这个标志接口是给JVM看的,虚拟机看到这个接口之后,会为实现类自动生成一个序列化版本号。
3、序列化版本号有什么用?
序列化版本号是让类实现了Serializable接口后,标志着这个类此时的版本。
若以后修改了类中代码,版本号又会改变,以前序列化的文件无法反序列化还原。
4、JVM是如何区分两个可序列化类的类型是否一致?
- 第一步:先比较两个类的完整类名,就是前面包括了包名。如果类名不一致,那就是两个不同的类型。
- 第二步:如果类名一致,再比较两个类的序列化版本号是否一致。
假设10年前,你把这个类的对象序列化到文件中。
10年后,因为业务需求而在这个类中添加了一个属性变量,这时序列化版本号会重新自动生成。
这时你再想把10年前的文件反序列化回来,就会报错,序列化版本号不一致。
如何解决这个问题?
把序列化版本号固定成一个常量值,这样的话你后期修改了代码,以前的文件还可以反序列化回来。
如何自定义序列化版本号?
我们通常定义成一个私有long型常量,放在类中的第一行。
且变量名固定为:serialVersionUID,如果不是这个名字的话,JVM还会自动生成一个序列化版本号,等于白做。
参考源代码ArrayList类中的第一行代码:
private static final long serialVersionUID = 8683452581122892189L;
结论:
一个类实现了Serializable接口,最好手动添加序列化版本号。
添加的序列化版本号最好不要和别的产生冲突。
提示:IDEA中可以设置提示生成序列化版本号。
File --> Settings --> Editor --> Inspections --> 搜Serializable class without serialVersionUID,勾上即可。
测试代码:
import java.io.*;
public class Test01 {
public static void main(String[] args) {
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
// 这里没有采用追加到文件末尾的方式,而是直接新建一个文件
oos = new ObjectOutputStream(new FileOutputStream("IOtest\\students"));
ois = new ObjectInputStream(new FileInputStream("IOtest\\students"));
// 准备两个可序列化对象,也可以序列化多个不同类型的对象,但要求类实现了Serializable接口
Student s1 = new Student(1001, "张三");
Student s2 = new Student(1002, "李四");
Object obj = new Object();
// 开始序列化写入
oos.writeObject(s1);
oos.writeObject(s2);
//oos.writeObject(obj); // 我们知道Object类并没有实现Serializable接口,不能序列化
// 刷新
oos.flush();
// 开始反序列化读取
Object obj1 = ois.readObject();
System.out.println(obj1); // Student{id=1001, name='张三'}
Object obj2 = ois.readObject();
System.out.println(obj2); // Student{id=1002, name='李四'}
} catch (IOException</