Java 原生序列化、JSON、Protobuf 有什么区别?怎么选?
结论先行:三者解决“对象 ↔ 字节/文本”的转换,取舍在性能、体积、可读性、跨语言与演进兼容之间。Java 原生(Serializable)接入成本最低但体积大、速度慢、含大量类元信息且无法跨语言;JSON 可读性与生态最好、跨语言普及,体积与解析性能居中;Protobuf 基于 IDL 生成代码,二进制紧凑、序列化快、跨语言好,但需要额外的 schema 与工具链。
对比表
| 维度 | Java 原生 | JSON | Protobuf |
|---|---|---|---|
| 格式 | 二进制 | 文本 | 二进制 |
| 体积 | 大 | 中 | 小 |
| 速度 | 慢 | 中 | 快 |
| 可读性 | 差 | 好 | 差 |
| 跨语言 | 差 | 好 | 好 |
| 演进兼容 | 靠 serialVersionUID | 字段增删较友好 | 字段号规则 + reserved |
| 典型场景 | 本地磁盘小对象 | Web API、日志、缓存文本 | RPC 内部通信 |
兼容性坑
- Java 原生:serialVersionUID 不一致直接抛 InvalidClassException,长期演进的类要显式声明并小心字段变更;
- JSON:字段改名是破坏性变更,多态信息默认丢失,需要自定义序列化配置;
- Protobuf:字段号一经发布不可复用,删除字段要先用 reserved 占位,防止老数据错位解析。
示例对比
// Java 原生:要求实现 Serializable
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("u.bin"))) {
oos.writeObject(user);
}
// JSON(Jackson):文本、可读
String json = objectMapper.writeValueAsString(user); // {"id":1,"name":"zhang"}
常见追问 / 记忆点
- 追问:为什么 RPC 内部不用 JSON?答:内部链路更看重体积与性能,且 schema 受控;JSON 主要服务外部接口的易用性。
- 追问:serialVersionUID 不写会怎样?答:JVM 会按类结构自动生成,结构一变版本号就变,老数据反序列化直接失败,所以建议显式声明。
- 记忆点:选型口诀“外部接口 JSON、内部 RPC Protobuf、本地小对象才 Java 原生”;兼容性靠 serialVersionUID / 字段规则守护。