Java默认的序列化流

引言:Java默认的序列化流

序列化流

序列化是什么?

序列化:把对象以流的方式写入到文件中保存,叫做写对象,也叫作对象的序列化

对象中包含的不仅仅是字符,还有字节,所以要用字节流

反序列化:把文件中保存的对象,以流的方式读取出来,叫做读对象,也叫作对象的反序列化

读取的文件保存的都是字节,使用字节流


实现序列化和反序列化的核心就是要使用:ObejctOutputStreamObjectInputStream

ObejctOutputStream

构造方法

1
2
ObjectOutputStream(OutputStream out);
//创建写入指定 OutputStream 的 ObjectOutputStream。

核心API

1
2
public final void writeObject(Object obj)throws IOException
//将指定的对象写入 ObjectOutputStream。

步骤

1
2
3
4
5
6
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
// 1. 创建ObjectOutputStream对象,构造方法中传递输出流
oos.writeObject(new Person("李白",18));
// 2. 使用ObjectOutputStream对象中的方法writeObejct把对象写入到文件中
oos.close();
// 3. 释放资源

运行报错NotSerializableException,这个错是未序列化报错,序列化和反序列化会抛出这个错误
我们必须实现一个标记性接口来启动这个序列化

标记性接口:实现这个接口不需要实现其任何方法

要进行序列化和反序列化的接口必须实现Serializable接口,就会给类添加一个标记

1
2
public class Person implements Serializable{
}

这样就可以运行了,输出的文件如下

1
2
3
4
5
6
7
aced 0005 7372 001d 636e 2e69 7463 6173
742e 6461 7930 342e 6465 6d6f 3031 2e50
6572 736f 6ef6 1bb3 45ad 3a82 6202 0002
4900 0361 6765 4c00 046e 616d 6574 0012
4c6a 6176 612f 6c61 6e67 2f53 7472 696e
673b 7870 0000 0012 7400 06e6 9d8e e799
bd

ObejctInputStream

核心API

构造方法:ObjectInputStream(InputStream in) 传入一个字节输入流

反序列化:readObject(),从输入流的位置读入文件,返回一个对象

小demo

1
2
3
4
5
6
7
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
// 1. 创建ObjectInputStream对象,构造方法传递字节输出流
Object o = ois.readObject();
// 2. 使用ObjectInputStream对象中的方法readObejct读取保存对象的文件
ois.close();
// 3. 释放资源
System.out.println(o);

注意:
可能会报出ClassNotFoundException这个错误,是因为不存在对象的class文件时抛出异常

所以反序列化必须实现两个东西

  1. 类必须实现Serializable
  2. 必须存在对应的Class文件

transient关键字

  • static关键字:

    static修饰的成员变量不能被序列化的

  • transient关键字:

    transient修饰的成员变量,不能被序列化

以后不想要成员变量被序列化,我们可以使用transient关键字修饰

InvalidClassException异常

当JVM反序列化对象时,能找到class对象,但是还会抛出一个InvalidClassException的异常,为什么?

有可能是因为

  1. 更改了class的内容,使得该类的序列版本号与读取到的类描述的版本号不匹配
  2. 该类包含未知数据类型
  3. 该类没有可访问的无参数构造

原理:实现了Serializable接口,就会根据类的定义,给该类一个接口的序列号,反序列化时会比照两者的序列号,如果曾更改了类,会使得他们没有匹配序列号,导致报错

解决方法:

  1. 无论是否对类的定义进行修改都不重新生成新的序列号
  2. 可以手动给类增加一个序列号
  • Serializable接口规定:

    可序列化类可以通过声明名为 serialVersionUID 的字段
    (该字段必须是static final long serialVersionUID 型字段)

在类中定义一个如下的成员变量即可

1
private static final long serialVersionUID = 1L;

序列化多个对象

​ 当我们想在文件中保存多个对象的时候,我们可以把多个对象存储到一个集合中,然后对集合进行序列化和反序列化

分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1 定义一个存储Person对象的ArrayList集合
ArrayList<Person> list = new ArrayList<>();
//2 往ArrayList集合中存储Person对象
list.add(new Person("李白",18));
list.add(new Person("李黑",20));
list.add(new Person("李太白",56));
//3 创建一个序列化ObejctOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
//4 使用ObjectOutputStream对象中的方法writeObejct,对集合进行序列化
oos.writeObject(list);
//5 创建一个反序列化ObejctInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
//6 使用ObjectInputStream的readObejct读取文件中保存的集合
Object o = ois.readObject();
//7 把Obejct类型的集合转换为ArrayList集合
ArrayList<Person> list1 = (ArrayList<Person>)o;
//8 遍历集合
for (Person person : list1) {
System.out.println(person);
}
//9 释放资源
ois.close();
oos.close();