知识模块
☕ Java 知识模块
五、Java IO/NIO
序列化与反序列化

序列化与反序列化

面试提问

"Java 序列化有几种方式?各有什么优缺点?"


什么是序列化

序列化:将对象转换为字节序列,便于存储或传输 反序列化:将字节序列恢复为对象

┌──────────┐     序列化      ┌──────────┐     传输/存储     ┌──────────┐
│   对象   │ ─────────────→ │  字节流   │ ───────────────→ │ 文件/网络 │
└──────────┘                └──────────┘                  └──────────┘

                                                                  ↓ 反序列化
┌──────────┐     反序列化    ┌──────────┐                    ┌──────────┐
│   对象   │ ←───────────── │  字节流   │ ←───────────────── │ 文件/网络 │
└──────────┘                └──────────┘                    └──────────┘

Java 原生序列化

实现方式

实现 Serializable 接口(标记接口,无方法)。

public class User implements Serializable {
    private static final long serialVersionUID = 1L;  // 版本号
    
    private String name;
    private int age;
    private transient String password;  // 不参与序列化
    
    // getters, setters
}
 
// 序列化
User user = new User("张三", 25, "123456");
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("user.dat"))) {
    oos.writeObject(user);
}
 
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("user.dat"))) {
    User user = (User) ois.readObject();
}

serialVersionUID

场景结果
未定义 serialVersionUIDJVM 自动生成,类修改后可能反序列化失败
定义 serialVersionUID显式指定,类修改后只要 ID 相同仍可反序列化

特点

优点缺点
使用简单性能差
支持对象图序列化后体积大
无需第三方依赖跨语言支持差
存在安全风险

常见序列化框架对比

框架性能体积跨语言特点
Java 原生简单、无需依赖
JSON可读性好、通用
ProtobufGoogle 出品、需定义 proto
HessianDubbo 默认
KryoJava 专用、高性能
FST比 Kryo 更快
MsgPack二进制 JSON

Protobuf(Protocol Buffers)

使用步骤

  1. 定义 .proto 文件
  2. 编译生成 Java 类
  3. 使用生成的类序列化/反序列化

proto 文件

syntax = "proto3";

message User {
    string name = 1;
    int32 age = 2;
}

Java 使用

// 序列化
User user = User.newBuilder()
    .setName("张三")
    .setAge(25)
    .build();
byte[] bytes = user.toByteArray();
 
// 反序列化
User user2 = User.parseFrom(bytes);

特点

  • 高性能:比 JSON 快 5-10 倍
  • 体积小:比 JSON 小 3-10 倍
  • 需预定义 schema:字段变更需重新编译

JSON 序列化

常用库

特点
JacksonSpring 默认,功能强大
GsonGoogle 出品,使用简单
Fastjson阿里出品,性能好(但有安全问题)

Jackson 示例

ObjectMapper mapper = new ObjectMapper();
 
// 序列化
User user = new User("张三", 25);
String json = mapper.writeValueAsString(user);
// {"name":"张三","age":25}
 
// 反序列化
User user2 = mapper.readValue(json, User.class);

特点

  • 可读性好:文本格式,便于调试
  • 跨语言:几乎所有语言都支持
  • 性能一般:解析和生成有开销
  • 体积较大:文本格式占用空间

Kryo

使用示例

Kryo kryo = new Kryo();
 
// 序列化
User user = new User("张三", 25);
try (Output output = new Output(new FileOutputStream("user.bin"))) {
    kryo.writeObject(output, user);
}
 
// 反序列化
try (Input input = new Input(new FileInputStream("user.bin"))) {
    User user2 = kryo.readObject(input, User.class);
}

特点

  • 高性能:比 Java 原生快 10 倍以上
  • 体积小:序列化后体积小
  • 仅 Java:不支持跨语言
  • 需要注册类:提高性能

Hessian

使用示例

// 序列化
User user = new User("张三", 25);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(user);
output.flush();
byte[] bytes = bos.toByteArray();
 
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
Hessian2Input input = new Hessian2Input(bis);
User user2 = (User) input.readObject();

特点

  • 跨语言:支持多种语言
  • Dubbo 默认:与 RPC 框架配合好
  • 性能中等:比 Java 原生好

序列化安全问题

Java 原生序列化的漏洞

// 危险!反序列化可能执行恶意代码
ObjectInputStream ois = new ObjectInputStream(input);
Object obj = ois.readObject();  // 可能触发恶意代码执行

防护措施

措施说明
避免使用 Java 原生序列化使用 JSON/Protobuf 替代
白名单校验只允许特定类反序列化
使用安全库如 Apache Commons IO

面试要点总结

问题答案要点
序列化作用?对象持久化、网络传输
transient 作用?标记字段不参与序列化
serialVersionUID 作用?版本控制,保证反序列化兼容
推荐哪种方式?跨语言用 Protobuf/JSON,Java 内部用 Kryo
Java 序列化安全问题?可能执行恶意代码,应避免使用

参考资料