序列化与反序列化
面试提问
"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
| 场景 | 结果 |
|---|---|
| 未定义 serialVersionUID | JVM 自动生成,类修改后可能反序列化失败 |
| 定义 serialVersionUID | 显式指定,类修改后只要 ID 相同仍可反序列化 |
特点
| 优点 | 缺点 |
|---|---|
| 使用简单 | 性能差 |
| 支持对象图 | 序列化后体积大 |
| 无需第三方依赖 | 跨语言支持差 |
| 存在安全风险 |
常见序列化框架对比
| 框架 | 性能 | 体积 | 跨语言 | 特点 |
|---|---|---|---|---|
| Java 原生 | 差 | 大 | 否 | 简单、无需依赖 |
| JSON | 中 | 中 | 是 | 可读性好、通用 |
| Protobuf | 优 | 小 | 是 | Google 出品、需定义 proto |
| Hessian | 良 | 中 | 是 | Dubbo 默认 |
| Kryo | 优 | 小 | 否 | Java 专用、高性能 |
| FST | 优 | 小 | 否 | 比 Kryo 更快 |
| MsgPack | 良 | 小 | 是 | 二进制 JSON |
Protobuf(Protocol Buffers)
使用步骤
- 定义
.proto文件 - 编译生成 Java 类
- 使用生成的类序列化/反序列化
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 序列化
常用库
| 库 | 特点 |
|---|---|
| Jackson | Spring 默认,功能强大 |
| Gson | Google 出品,使用简单 |
| 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 序列化安全问题? | 可能执行恶意代码,应避免使用 |