跳转至

什么是序列化?如何序列化?为什么要序列化?反序列化会遇到哪些问题?

一、什么是序列化

把对象转成字节流,以便: - 保存到磁盘。 - 网络传输。

反序列化就是把字节流恢复成对象。

二、Java 序列化

1. 实现 Serializable 接口

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age;   // transient 不序列化
}

2. 为什么要 serialVersionUID

显式声明版本号。反序列化时如果版本不一致,抛 InvalidClassException

不声明的话,JVM 根据类结构自动生成,类一改就变。

3. transient

被 transient 修饰的字段不序列化。反序列化后是默认值(0/null)。

4. 序列化方式

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("u.bin"));
oos.writeObject(user);

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("u.bin"));
User u = (User) ois.readObject();

三、为什么要序列化

  1. 网络传输:RPC、RMI。
  2. 持久化:对象存磁盘、缓存。
  3. 深拷贝:序列化再反序列化。

四、反序列化的问题

1. 版本不一致

类改了字段,旧字节流反序列化失败。

2. 安全漏洞

反序列化不可信数据可能执行恶意代码(Fastjson、Jackson 历史漏洞)。

  • 不要反序列化不可信数据。
  • 用白名单。

3. transient 字段丢失

反序列化后 transient 字段是默认值。

4. 静态字段不序列化

static 属于类,不属于对象。

5. 继承关系

父类实现 Serializable,子类自动实现;子类实现,父类不实现,父类字段用默认构造初始化。

6. 性能

Java 原生序列化慢,体积大。常用: - JSON - Protobuf - Hessian - Kryo

五、最佳实践

  • 显式声明 serialVersionUID。
  • 敏感字段用 transient。
  • 不要用原生序列化做跨语言。
  • 用 JSON/Protobuf。

高频追问

  • 为什么序列化不保存 static?static 是类级别的。
  • 为什么要实现 Serializable 接口?只是标记,JVM 看到才允许序列化。