Java 序列化机制详解
序列化是将对象转换为字节流的过程,用于网络传输和持久化存储。
一、序列化基础
1.1 什么是序列化
序列化 = 对象 → 字节流
用途:
- 网络传输(RPC、消息队列)
- 持久化存储(文件、数据库)
- 缓存(Redis、Memcached)
- 深拷贝
1.2 Serializable
// 实现 Serializable 接口
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 无参构造
public User() {}
// getter/setter
}
// 序列化
User user = new User("Alice", 20);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.ser"))) {
oos.writeObject(user);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.ser"))) {
User deserialized = (User) ois.readObject();
}
1.3 serialVersionUID
// 作用:版本控制
private static final long serialVersionUID = 1L;
// 不指定的后果:
// 类修改后,无法反序列化(InvalidClassException)
// 建议:始终显式声明
二、Externalizable(自定义序列化)
2.1 实现 Externalizable
public class User implements Externalizable {
private String name;
private int age;
// 必须有 public 无参构造
public User() {}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 自定义序列化逻辑
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 自定义反序列化逻辑
name = in.readUTF();
age = in.readInt();
}
}
2.2 Serializable vs Externalizable
| 特性 | Serializable | Externalizable |
|---|---|---|
| 实现 | 标记接口 | 需实现方法 |
| 控制 | 自动 | 手动控制 |
| 性能 | 低 | 高 |
| 大小 | 大 | 小 |
| 适用 | 一般场景 | 高性能需求 |
三、transient 关键字
3.1 忽略字段
public class User implements Serializable {
private String name;
private transient String password; // 不序列化
private transient Connection conn; // 资源不序列化
// 反序列化后密码为 null
}
3.2 静态字段
public class Config implements Serializable {
private static final String VERSION = "1.0"; // 不序列化
private String data; // 序列化
}
四、序列化问题
4.1 单例破坏
// ❌ 序列化破坏单例
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
// 反序列化会创建新实例
// ✅ 修复:readResolve
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
// 反序列化时返回已有实例
private Object readResolve() {
return INSTANCE;
}
}
4.2 继承序列化
// 父类实现 Serializable
public class Parent implements Serializable {
private String parentField;
}
// 子类自动可序列化
public class Child extends Parent implements Serializable {
private String childField;
}
// 父类未实现 Serializable
public class Parent { // 未实现
private String parentField;
}
public class Child extends Parent implements Serializable {
// 反序列化时,父类字段为默认值
}
4.3 集合序列化
// 集合中的元素必须可序列化
public class Team implements Serializable {
private List<User> members; // User 必须实现 Serializable
}
五、Protobuf 序列化
5.1 为什么用 Protobuf
Protobuf 优势:
- 体积小(比 Java 序列化小 3-10 倍)
- 速度快(比 Java 序列化快 20-100 倍)
- 跨语言
- 向前/向后兼容
5.2 定义 proto
// user.proto
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated string tags = 4;
}
5.3 生成代码
# 编译 proto
protoc --java_out=. user.proto
5.4 使用示例
// 添加依赖
// <dependency>
// <groupId>com.google.protobuf</groupId>
// <artifactId>protobuf-java</artifactId>
// <version>3.21.7</version>
// </dependency>
// 创建对象
UserProto.User user = UserProto.User.newBuilder()
.setId(1)
.setName("Alice")
.setEmail("alice@example.com")
.addTags("admin")
.addTags("user")
.build();
// 序列化
byte[] data = user.toByteArray();
// 反序列化
UserProto.User parsed = UserProto.User.parseFrom(data);
六、其他序列化协议
6.1 JSON
// Jackson
ObjectMapper mapper = new ObjectMapper();
// 序列化
String json = mapper.writeValueAsString(user);
// 反序列化
User user = mapper.readValue(json, User.class);
6.2 Hessian
// Hessian 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(baos);
ho.writeObject(user);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
HessianInput hi = new HessianInput(bais);
User user = (User) hi.readObject();
6.3 Kryo
// Kryo 序列化
Kryo kryo = new Kryo();
kryo.register(User.class);
// 序列化
Output output = new Output(new ByteArrayOutputStream());
kryo.writeObject(output, user);
output.close();
// 反序列化
Input input = new Input(new ByteArrayInputStream(output.toBytes()));
User user = kryo.readObject(input, User.class);
七、协议对比
7.1 性能对比
| 协议 | 大小 | 速度 | 跨语言 | 适用场景 |
|---|---|---|---|---|
| Java 序列化 | 大 | 慢 | ❌ | 内部系统 |
| Protobuf | 小 | 快 | ✅ | RPC、存储 |
| JSON | 中 | 中 | ✅ | Web API |
| Hessian | 中 | 快 | ✅ | Java RPC |
| Kryo | 小 | 快 | ❌ | Java 内部 |
7.2 选择建议
// ✅ Java 内部、简单场景
Serializable
// ✅ 跨语言、高性能
Protobuf / Thrift
// ✅ Web API
JSON (Jackson / Gson)
// ✅ Java RPC
Hessian / Kryo
// ✅ 大数据
Avro / Parquet
八、最佳实践
8.1 序列化安全
// ✅ 敏感字段用 transient
public class User implements Serializable {
private String name;
private transient String password;
}
// ✅ 验证反序列化
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// 验证字段
if (name == null || name.isEmpty()) {
throw new InvalidObjectException("Invalid name");
}
}
8.2 版本兼容
// ✅ 使用 serialVersionUID
private static final long serialVersionUID = 1L;
// ✅ 新增字段要有默认值
public class User implements Serializable {
private String name;
private String email; // 新增字段,默认 null
// 旧版本序列化的对象可反序列化
}
8.3 性能优化
// ✅ 避免大对象序列化
public class Cache implements Serializable {
private transient LargeObject cache; // 不序列化
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
// 只序列化必要数据
}
}
// ✅ 使用 Externalizable 自定义
// ✅ 使用 Protobuf 等高效协议
九、总结
序列化核心要点:
| 协议 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Serializable | 简单、内置 | 慢、大 | Java 内部 |
| Externalizable | 可控、高效 | 复杂 | 高性能 |
| Protobuf | 小、快、跨语言 | 需编译 | RPC、存储 |
| JSON | 可读、通用 | 较大 | Web API |
序列化协议选择:跨语言用 Protobuf,Web 用 JSON,Java 内部用 Serializable。