在 Java 编程中,序列化是一个非常重要的概念,它允许我们将对象转换为字节流,以便于存储或传输。反序列化则是将字节流转换回对象的过程。尽管我们通常只需要让类实现 Serializable
接口即可实现序列化,但深入了解其背后的机制和细节,对于提升我们的编程技能和理解 Java 的内部工作原理非常有帮助。
1 理论基础
Java 序列化是在 JDK 1.1 中引入的特性,用于将 Java 对象转换为字节数组,便于存储或传输。反序列化则是将字节数组转换回 Java 对象的过程。
序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。
要序列化的对象必须实现 Serializable
接口,否则会抛出 NotSerializableException
异常。
Serializable 接口定义
public interface Serializable {
}
Serializable
接口是一个空接口,没有任何方法。它的作用仅仅是作为一个标识,告诉 Java 虚拟机该类的对象是可以被序列化的。
2 实战演练
首先,我们创建一个简单的类 Wanger
,包含两个字段 name
和 age
,并实现 Serializable
接口。
class Wanger implements Serializable {
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Wanger{" + "name=" + name + ",age=" + age + "}";
}
}
接下来,我们创建一个测试类,通过 ObjectOutputStream
将 Wanger
对象序列化到文件中,再通过 ObjectInputStream
从文件中反序列化对象。
public class Test {
public static void main(String[] args) {
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
3 序列化与反序列化的内部机制
ObjectOutputStream
在序列化对象时,会依次调用 writeObject()
→ writeObject0()
→ writeOrdinaryObject()
→ writeSerialData()
→ invokeWriteObject()
→ defaultWriteFields()
。
ObjectInputStream
在反序列化对象时,会依次调用 readObject()
→ readObject0()
→ readOrdinaryObject()
→ readSerialData()
→ defaultReadFields()
。
3.1 defaultWriteFields() 方法
private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException {
// 获取对象的类,并检查是否可以进行默认的序列化
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize();
// 获取对象的基本类型字段的数量,以及这些字段的值
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
// 将基本类型字段的值写入输出流
bout.write(primVals, 0, primDataSize, false);
// 获取对象的非基本类型字段的值
ObjectStreamField[] fields = desc.