一、对象流与序列化
1、简介
对象流,包括对象输入流和对象输出流,流中流动的是对象。
- ObjectOutputStream:对象输出流(内存—>硬盘),将一个对象写入到本地文件,被称为对象的序列化。
- ObjectInputStream:对象输入流(硬盘—>内存),将一个本地文件中的对象读取出来,被称为对象的反序列化。
注意:
- 一个对象流只能操作一个对象,如果试图采用一个对象流操作多个对象的话,会出现EOFException【End Of File:文件意 外达到了文件末尾】
- 如果向将多个对象序列化到本地,可以借助于集合,【思路:将多个对象添加到集合中,将集合的对象写入到 本地文件中,再次读出来,获取到的仍然是集合对象,遍历集合】
2、ObjectOutputStream(序列化)
(1)继承关系
java.lang.Object
|____java.io.OutputStream
|____java.io.ObjectOutputStream
(2)类声明
public class ObjectOutputStream
extends OutputStream implements ObjectOutput, ObjectStreamConstants
{}
(3)构造方法
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
protected ObjectOutputStream() throws IOException, SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
bout = null;
handles = null;
subs = null;
enableOverride = true;
debugInfoStack = null;
}
ObjectOutputStream 有两个构造方法,一个公有的,接受一个OutputStream 类型的参数,另一个构造方法是受保护的无参的构造方法。
(4)简单使用
创建一个Student类,用于序列化:
package basis.StuIO.StuObjectStream;
public class Student{
private String name;
private String address;
public Student(String name, String address) {
this.name = name;
this.address = address;
}
public Student() {
}
//省略getter和setter
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
主类(测试类):
package basis.StuIO.StuObjectStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class StuObjectStream {
public static void main(String[] args) throws IOException {
//创建待序列化的对象
Student suxing = new Student("苏星","北京");
//创建用于序列化的对象输出流,将对象写入到项目根目录下的一个二进制文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.bin"));
oos.writeObject(suxing);
System.out.println("序列化完成。");
}
}
运行发现报异常:
Exception in thread "main" java.io.NotSerializableException: basis.StuIO.StuObjectStream.Student
告诉我们,这个对象不能被序列化,原来,序列化不仅仅只使用 ObjectOutputStream 流输出对象这么简单,还要求我们需要序列化对象的类实现 Serializable 接口;
Serializable 接口是一个标记接口,接口中没有任何字段和方法,我们只需要实现即可。标志着这个;类的对象可以被序列化。
修改Student类:
public class Student implements Serializable {}
其他不变,运行程序。
控制台输出 “序列化完成。” 并且,在项目根目录找到名为 “stu.bin” 的二进制文件。标志着序列化成功。
3、ObjectInputStream(反序列化)
(1)继承关系
java.lang.Object
|____java.io.InputStream
|____ava.io.ObjectInputStream
(2)类声明
public class ObjectInputStream
extends InputStream implements ObjectInput, ObjectStreamConstants
{}
(3)构造方法
public ObjectInputStream(InputStream in) throws IOException {
verifySubclass();
bin = new BlockDataInputStream(in);
handles = new HandleTable(10);
vlist = new ValidationList();
serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
}
protected ObjectInputStream() throws IOException, SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
bin = null;
handles = null;
vlist = null;
serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = true;
}
和ObjectOutputStream 一样,ObjectInputStream 也拥有两个构造方法,一个接受InputStream 类型的有参公有构造,一个无参的受保护的构造方法。
(4)反序列化实例
我们对上面序列化得到的二进制文件进行反序列化,并输出结果观察:
package basis.StuIO.StuObjectStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class StuObjectStream {
public static void main(String[] args) throws Exception {
Student student = null;
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.bin"));
student = (Student) ois.readObject();
System.out.println(student);
}
}
输出结果:
Student{name='苏星', address='北京'}
跟我们之前创建的Student对象一致,说明我们反序列化成功了。
4、SerializableUID
我们如上面的示例,先序列化一个对象,得到该对象的二进制文件。
然后,我们修改 Student 类,在类中添加一个属性 age,添加相应的 getter和setter,重写toString() 方法。
然后再对各刚刚得到的二进制文件进行反序列化,会发现反序列化不成功,控制台报 InvalidClassException 异常:
Exception in thread "main" java.io.InvalidClassException:
basis.StuIO.StuObjectStream.Student; local class incompatible: stream classdesc
serialVersionUID = 2809223061937765453, local class serialVersionUID = -2338080562382343211
它告诉我们两个类的 serializableUID 值不一样,JVM告诉我们进行反序列化的对象的类,和我们接收的Student类不是同一个类,所以序列化不成功。
解决:
serializableUID 是一个类的序列化版本,每个类的该值都不相同。为我们的Student类添加该字段后,才能保证对象在序列化之后被修改,然后再进行序列化时,两个对象的序列化版本一致。
在添加age字段之前,为Student类添加 serializableUID :
idea快速添加SerializableUID的方法:https://blog.csdn.net/qq_36090419/article/details/80626489
public class Student implements Serializable {
private static final long serialVersionUID = 7967100133902769024L;
private String name;
private String address;
//private int age;
}
重复我们第一次对Student 对象的序列化,得到stu.bin文件。
然后按照上述的修改Student 类,添加age字段和对应方法,然后对 stu.bin 文件进行反序列化。控制台输出结果如下:
Student{name='苏星', address='北京', age=0}
可见,我们序列化成功了,age属性默认为0。
5、transient 和 static 修饰的字段
开门见山,被transient修饰的字段是不参与序列化的,该关键字也为此而生。
但是,被 static 修饰的字段也不参与序列化。
我们为 address 字段 添加 static 关键字,用相同的方式进行序列化,然后进行反序列化,控制台输出结果:
Student{name='苏星', address='null', age=21}
我们发现 address 字段为 null ,说明被static 修饰的 address 没有参与序列化。
transient 关键字与static类似,transient修饰的字段也不参与对象的序列化。
6、同时序列化多个对象
方法一:顺序序列化
同时调用多个序列化方法,把多个对象序列化到一个.bin文件中。
示例:
序列化
package basis.StuIO.StuObjectStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class StuObjectStream {
public static void main(String[] args) throws Exception {
//序列化
//创建待序列化的对象
Student zhangsan = new Student("张三","北京",21);
Student lisi = new Student("李四","天津",22);
Student wangwu = new Student("王五","南京",24);
//创建用于序列化的对象输出流,将对象写入到项目根目录下的一个二进制文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.bin"));
oos.writeObject(zhangsan);
oos.writeObject(lisi);
oos.writeObject(wangwu);
System.out.println("序列化完成。");
}
}package basis.StuIO.StuObjectStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class StuObjectStream {
public static void main(String[] args) throws Exception {
//序列化
//创建待序列化的对象
Student zhangsan = new Student("张三","北京",21);
Student lisi = new Student("李四","天津",22);
Student wangwu = new Student("王五","南京",24);
//创建用于序列化的对象输出流,将对象写入到项目根目录下的一个二进制文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.bin"));
oos.writeObject(zhangsan);
oos.writeObject(lisi);
oos.writeObject(wangwu);
System.out.println("序列化完成。");
}
}
反序列化
package basis.StuIO.StuObjectStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class StuObjectStream {
public static void main(String[] args) throws Exception {
//反序列化
Student zhangsan = null;
Student lisi = null;
Student wangwu = null;
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.bin"));
zhangsan = (Student) ois.readObject();
lisi = (Student) ois.readObject();
wangwu = (Student) ois.readObject();
System.out.println(zhangsan);
System.out.println(lisi);
System.out.println(wangwu);
System.out.println("反序列化完成。");
}
}
控制台输出:
Student{name='张三', address='null', age=21}
Student{name='李四', address='null', age=22}
Student{name='王五', address='null', age=24}
反序列化完成。
由结果可以看出,序列化多个对象成功了,因为address字段是 static 的,所以不参与序列化,这里反序列化时,该字段为空。
方法二、借助集合:
创建多个对象放入集合中,然后对集合进行序列化。
有集合的类声明可以看出,ArrayList 类已经实现了Serializable 接口,并且添加了序列化版本号。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
}
示例:
序列化:
package basis.StuIO.StuObjectStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class StuListSerialiazble {
public static void main(String[] args) throws Exception{
Student zhangsan = new Student("张三","北京",21);
Student lisi = new Student("李四","天津",22);
Student wangwu = new Student("王五","南京",24);
List<Student> students = new ArrayList<>();
students.add(zhangsan);
students.add(lisi);
students.add(wangwu);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stuList.bin"));
oos.writeObject(students);
System.out.println("序列化完成。");
}
}
反序列化:
package basis.StuIO.StuObjectStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class StuListSerialiazble {
public static void main(String[] args) throws Exception{
List<Student> students = new ArrayList<>();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stuList.bin"));
students = (List<Student>) ois.readObject();
Iterator<Student> it = students.iterator();
while(it.hasNext()){
System.out.println(it.next().toString());
}
}
}
控制台输出:
Student{name='张三', address='null', age=21}
Student{name='李四', address='null', age=22}
Student{name='王五', address='null', age=24}
反序列化成功。