Skip to content

序列化协议详解


对象(内存) ── 序列化 ──> 字节数组(磁盘/网络)
字节数组(磁盘/网络)── 反序列化 ──> 对象(内存)
Client Server
│ │
│ 序列化 Request │
│ ─────────────────────────> │
│ │ 反序列化 Request
│ │ ──────┐
│ │ │
│ │ 执行业务
│ │ │
│ │ <─────┘
│ │ 序列化 Response
│ <───────────────────────── │
│ 反序列化 Response │

// 序列化
User user = new User(1, "张三");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.txt"));
out.writeObject(user);
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.txt"));
User user = (User) in.readObject();

缺点

  • 性能差(反射机制)
  • 安全问题(可执行任意类)
  • 跨语言困难
  • 序列化后体积大
// 序列化
Hessian2Output out = new Hessian2Output(byteArrayOutputStream);
out.writeObject(user);
// 反序列化
Hessian2Input in = new Hessian2Input(new ByteArrayInputStream(bytes));
User user = (User) in.readObject();

优点

  • 性能好(自定义序列化)
  • 跨语言支持
  • 序列化体积小

缺点

  • 不支持循环引用
  • 部分类型支持有限
// 序列化
Kryo kryo = new Kryo();
Output output = new Output(new FileOutputStream("user.txt"));
kryo.writeObject(output, user);
// 反序列化
Input input = new Input(new FileInputStream("user.txt"));
User user = kryo.readObject(input, User.class);

优点

  • 序列化速度极快
  • 序列化体积小
  • 支持循环引用

缺点

  • 跨语言支持差
  • 线程不安全(需要 ThreadLocal)
// 定义 proto 文件
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
// 生成 Java 代码
// protoc --java_out=. user.proto
// 序列化
UserProto.User user = UserProto.User.newBuilder()
.setId(1)
.setName("张三")
.setEmail("zhangsan@example.com")
.build();
byte[] bytes = user.toByteArray();
// 反序列化
UserProto.User user = UserProto.User.parseFrom(bytes);

优点

  • 序列化/反序列化速度极快
  • 序列化体积最小
  • 跨语言支持好
  • 向后兼容性好

缺点

  • 需要预先定义 IDL 文件
  • 学习成本高
// 序列化
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
// 反序列化
User user = mapper.readValue(json, User.class);

优点

  • 可读性好
  • 跨语言天然支持
  • HTTP 友好

缺点

  • 序列化体积大
  • 序列化/反序列化速度慢
  • 不支持循环引用

序列化方式序列化速度反序列化速度体积跨语言
Java 原生1x1x1x
Hessian3x3x0.5x
Kryo10x10x0.3x
Protobuf15x15x0.2x
JSON2x2x2x

注:以上为大致对比,实际性能受数据结构和 JVM 影响


┌─────────────────────────────────────────────────────────────┐
│ 序列化选型决策树 │
└─────────────────────────────────────────────────────────────┘
是否需要跨语言?
├─ 是 ──> 是否对性能要求极高?
│ │
│ ├─ 是 ──> Protobuf
│ │
│ └─ 否 ──> JSON
└─ 否 ──> 是否对性能要求极高?
├─ 是 ──> Kryo / Protobuf
└─ 否 ──> Java 原生 / Hessian
场景推荐序列化
内部服务调用(高性能)Kryo
跨语言高性能调用Protobuf
对外 APIJSON
内部 Java 服务Hessian / Kryo
大数据量传输Protobuf

Q1: 为什么 Java 原生序列化不推荐使用?

Section titled “Q1: 为什么 Java 原生序列化不推荐使用?”

参考答案

  1. 性能差:使用反射机制,开销大
  2. 安全问题:可以反序列化任意类,存在安全漏洞
  3. 序列化体积大:包含大量元数据
  4. 跨语言困难:其他语言难以解析

参考答案

  • 优点:序列化速度快,体积小,支持循环引用
  • 缺点:跨语言支持差,线程不安全(需使用 ThreadLocal)

参考答案

  1. 采用二进制格式,存储紧凑
  2. 编码/解码只需要简单的位移操作
  3. 预先定义 IDL,不需要额外的类型信息
  4. 支持向前/向后兼容(Tag-Wire 格式)