Java 原生序列化、JSON、Protobuf 有什么区别?怎么选?

结论先行:三者解决“对象 ↔ 字节/文本”的转换,取舍在性能、体积、可读性、跨语言与演进兼容之间。Java 原生(Serializable)接入成本最低但体积大、速度慢、含大量类元信息且无法跨语言;JSON 可读性与生态最好、跨语言普及,体积与解析性能居中;Protobuf 基于 IDL 生成代码,二进制紧凑、序列化快、跨语言好,但需要额外的 schema 与工具链。

对比表

维度Java 原生JSONProtobuf
格式二进制文本二进制
体积
速度
可读性
跨语言
演进兼容靠 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 / 字段规则守护。
笔记加载中…