【Java I/O 流】4 - 7 序列化与反序列化
§4-7 序列化与反序列化
4-7.1 字节流的序列化与反序列化流
序列化流与反序列化流是字节流的高级流,其体系如下如所示:
序列化和反序列化是针对 Java 对象而言的。序列化允许将 Java 对象的数据写到本地文件中,而反序列化则允许将本地文件中的 Java 对象数据还原。
4-7.2 序列化:对象输出流
ObjectOutputStream 可以把 Java 对象写到本地文件中。
构造方法:
| 构造方法 | 描述 |
|---|---|
ObjectOutputStream(OutputStream out) |
创建一条关联指定输出流的对象输出流 |
-
序列化流属于高级流,该构造方法要求传入一个基本流对象;
为了效率,一般考虑在参数中包装基本流(
new); -
关闭流释放资源时,只需要关闭高级流即可,方法内部会自行关闭所依赖的基本流;
序列化方法:
| 方法 | 描述 |
|---|---|
void write(byte[] buf) |
写出一个字节数组 |
void write(byte[] buf, int off, int len) |
写出一个字节子数组 |
void write(int val) |
写出一个字节 |
void writeBoolean(boolean val) |
写出一个布尔值 |
void writeByte(int val) |
写出一个 8 位字节 |
void writeBytes(String str) |
将字符串作为字节序列写出 |
void writeChar(int val) |
写出一个 16 位字符 |
void writeChars(String str) |
将字符串作为字符序列写出 |
void writeDouble(double val) |
写出一个 64 位双精度浮点数 |
void writeFloat(double val) |
写出一个 32 位单精度浮点数 |
void writeInt(int val) |
写出一个 32 位整型 |
void writeLong(long val) |
写出一个 64 位长整型 |
void writeShort(int val) |
写出一个 16 位短整型 |
void writeObject(Object obj) |
将指定对象写到流上 |
-
序列化也支持基本数据类型,但建议使用基于对象的序列化;
-
序列化后的数据,对于人类不可读,或难以阅读;
-
若要序列化 Java 对象,该对象的类必须实现
Serializable接口,序列化会自动进行;该接口是一个标志性接口,其中没有任何方法;实现此接口的类允许被序列化,否则将会抛出异常
NotSerializableException; -
若要指定 Java Bean 中哪些成员变量不被序列化,可在该变量前用瞬态关键字
transient修饰即可(仅Serializable实现类);另外,调用
writeObject时,静态成员也无法被序列化;序列化引用成员,该引用成员的类必须满足可序列化要求,即实现
Serializable接口; -
类也可以选择实现
Externalizable接口实现序列化,该接口继承了Serializable,用于手动选择序列化的进行;该接口有两个无返回值的抽象方法
readExternal(ObjectInput in)和writeExternal(ObjectOut out),必须重载这两个方法以指定序列化与反序列化的成员;使用该接口可以更灵活地指定需要序列化的对象和序列化顺序;实现了
Externalizable的类需要进行序列化与反序列化时,通过实例调用其重载的writeExternal和readExternal方法即可,重载方法的实现应当避免使用writeObject和readObject,而是使用其他方法选择序列化与反序列化的成员(如writeInt,readInt等);必须保证序列化与反序列化的数据顺序相同,否则抛出异常;
为保证反序列化能够调用成员进行,建议为类提供一个无参构造;
-
序列化得到的文件不能修改,一旦修改,则无法反序列化;
4-7.3 反序列化:对象输入流
ObjectInputStream 可以将由 ObjectOutputStream 序列化的 Java 对象还原为 Java 对象。
构造方法:
| 构造方法 | 描述 |
|---|---|
ObjectInputStream(InputStream in) |
创建一条关联指定输入流的对象输入流 |
-
序列化流属于高级流,该构造方法要求传入一个基本流对象;
为了效率,一般考虑在参数中包装基本流(
new); -
关闭流释放资源时,只需要关闭高级流即可,方法内部会自行关闭所依赖的基本流;
反序列化方法:
| 方法 | 描述 |
|---|---|
int read() |
读取一个字节 |
int read(byte[] buf, int off, int len) |
将数据读取到字节数组中 |
boolean readBoolean() |
读取一个布尔值 |
byte readByte() |
读取一个 8 位字节 |
char readChar() |
读取一个 16 位字符 |
double readDouble() |
读取一个 64 位双精度浮点数 |
float readFloat() |
读取一个 32 位单精度浮点数 |
int readInt() |
读取一个 32 位整型 |
long readLong() |
读取一个 64 位长整型 |
short readShort() |
读取一个 16 位短整型 |
Object readObject() |
读取一个来自对象输出流的对象 |
-
反序列化也支持基本数据类型,但建议使用基于对象的反序列化;
-
读取对象可能会抛出编译时异常
ClassNotFoundException,且该方法读到文件末尾处再次调用会抛出异常EOFException; -
序列化与反序列化时,都会根据对应 Java Bean 类的变量和方法计算该类的序列版本 UID(
serialVersionUID);若在反序列化时发现计算得出的版本号与序列化时的版本号不一致,则会抛出异常
InvalidClassException;为解决此问题,只需要在 Java Bean 中使用常量固定其版本号即可,固定方法只能是:
@java.io.Serial private static final long serialVersionUID = XXXXXXL;其中,
@java.io.Serial是注解。版本号一旦固定,则不应再更改。版本号可由类的属性和方法计算得出,这一步骤可用 IDEA 自动生成;IDEA 自动警告未指定序列版本的设置方法:在设置中搜索
Serial,勾选没有 serialVersionUID 的可序列化非 static 内部类和不带 serialVersionUID 的可序列化类选项;实现了
Externalizable接口的类也应当指定版本号;
4-7.4 案例演示
需求:序列化多个自定义对象(Student,但个数不确定),并反序列化后输出。
可以让 Java Bean 实现 Serializable 后,将多个对象放到集合中,将集合序列化即可。
import java.io.*;
import java.util.ArrayList;
import java.util.Random;
public class Serialize {
public static void main(String[] args) {
String[] names = {"张三", "李四", "王五", "赵六", "陈七", "钱八", "秦九", "马十"};
String[] addresses = {"广州", "上海", "深圳", "北京", "重庆", "杭州", "南京", "厦门"};
ArrayList<Student> list = new ArrayList<>();
Random rnd = new Random();
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("JavaSE\\aaa\\objout.txt"));
int loops = rnd.nextInt(10) + 1;
System.out.println("循环次数:" + loops);
for (int i = 0; i < loops; i++) {
Student student = new Student(names[rnd.nextInt(names.length)]
, rnd.nextInt(31)
, addresses[rnd.nextInt(addresses.length)]);
list.add(student);
}
oos.writeObject(list); //将集合写入即可
oos.close();
}
}
反序列化时,也直接反序列化集合即可。
public class Deserialize {
public static void main(String[] args) {
//反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("JavaSE\\aaa\\objout.txt"));
ArrayList<Student> res = (ArrayList<Student>) ois.readObject();
ois.close();
System.out.println(res);
}
}
4-7.5 Externalizable 与 Serializable 接口用法
两个接口都可以用于序列化与反序列化,先重复一遍二者的共同点:
- 实现类都可以被序列化;
- 实现类都应当固定版本号(
serialVersionUID)以防抛出类无效异常;
而它们的不同点在于:
-
Serializable是标记类接口,无抽象方法,序列化与反序列化自动进行;通过调用对象输入输出流中的
writeObject和readObject完成序列化与反序列化; -
Externalizable是Serializable的子接口,其中有两个抽象方法:void writeExternal(ObjectOut out); //写出 void readExternal(ObjectIn in); //读取实现了该接口的方法应当重载这两个方法,并通过成员调用这两个方法完成对象的序列化与反序列化;
读写要配套,读写数据顺序必须保持一致,且建议提供一个无参构造器;
-
Serializable的实现类在序列化时会跳过由transient和static修饰的成员; -
Externalizable的实现类在序列化时更加灵活,是否序列化完全取决于方法的实现,而非成员变量的修饰符;
示例一:Serializable 自动序列化
Serializable 的实现类:
package com.io.serial;
import java.io.*;
public class Student implements Serializable {
//固定版本号,防止 JavaBean 修改后重新计算版本号导致版本号不匹配
@Serial
private static final long serialVersionUID = -7214721743963933347L;
private String name;
private int age;
private transient String address; //瞬态关键字,不进行序列化
//构造器、Getters & Setters等方法省略
}
在测试类中:
import com.io.serial.Student;
import java.io.*;
public class SerialDemo {
public static void main(String[] args) {
Student std1 = new Student("张三", 23, "广州");
Student std2 = new Student("李四", 24, "深圳");
//序列化
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("output\\serial.txt"));
oos.writeObject(std1);
oos.writeObject(std2);
oos.close;
//反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("output\\serial.txt"));
Object o1 = ois.readObject();
Object o2 = ois.readObject();
ois.close();
//输出反序列化结果
System.out.println(o1);
System.out.println(o2);
}
}
输出结果为:
张三(23, null)
李四(24, null)
静态变量与瞬态变量并未序列化到文件中。
示例二:Externalizable 手动序列化
Externalizable 实现类:
package com.io.serial;
import java.io.*;
public class Teacher implements Externalizable {
@Serial
private static final long serialVersionUID = 888902281129162579L;
private String name;
private int age;
private transient String address;
//构造器、Getters & Setters等方法省略
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(this.name);
out.writeInt(this.age);
out.writeUTF(this.address);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = in.readUTF();
this.age = in.readInt();
this.address = in.readUTF(); //读写数据的顺序要保证相同
}
}
在测试类中:
import java.io.*;
public class Externalize {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Externalize 接口测试
Teacher tcr1 = new Teacher("张三", 23, "广州");
Teacher tcr2 = new Teacher("李四", 24, "深圳");
Teacher rec1 = new Teacher();
Teacher rec2 = new Teacher();
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("JavaSE\\aaa\\externalize.txt"));
tcr1.writeExternal(oos);
tcr2.writeExternal(oos);
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("JavaSE\\aaa\\externalize.txt"));
rec1.readExternal(ois);
rec2.readExternal(ois);
ois.close();
//输出反序列化结果
System.out.println(rec1);
System.out.println(rec2);
}
}
输出结果:
张三(23, 广州)
李四(24, 深圳)
Externalizable 的灵活度更高,可手动决定哪些变量可以序列化。
浙公网安备 33010602011771号