Skip to content

序列化:数据的"翻译"

🎯 核心问题

数据如何在网络上传输? 这就像问:一个人说的话,如何让另一个人听懂?序列化解决的就是"数据翻译"的问题——把内存中的对象翻译成可以传输的格式。


序列化数据的必要性

在前后端交互过程中,数据需要经历多次"变形"才能从服务器传递到客户端。

场景一:前端收到的数据"变了"

javascript
// 后端发送
Date birth = new Date(1990, 5, 15)

// 前端收到
{ "birth": "1990-06-15T00:00:00Z" }  // 字符串!

前端想用 .getFullYear(),结果报错了——因为这不是 Date 对象,是字符串。

场景二:中文乱码

json
// 期望
{ "name": "张三" }

// 实际收到
{ "name": "å¼ ä¸" }

字符编码问题导致中文变成乱码。

场景三:性能瓶颈

json
// 一个包含 10000 条商品列表的响应
{
  "products": [
    { "id": 1, "name": "...", "description": "...", ... },
    // ... 9999 more
  ]
}
// 大小:5.2 MB,传输时间:3.5 秒

JSON 格式的冗余导致数据包太大,严重影响性能。


序列化就像"翻译"——把内存对象"翻译"成可以传输的格式,接收方再"翻译"回去。


1. 什么是序列化/反序列化?

序列化(Serialization)就是把对象转换成可传输格式的过程。

反序列化(Deserialization)就是把传输格式还原成对象的过程。

1.1 用寄快递来类比

寄快递序列化说明
打包物品序列化把物品装箱,贴上标签
运输网络传输快递车运送到目的地
拆包取物反序列化收件人打开箱子,取出物品

1.2 为什么需要序列化?

原因说明示例
网络传输网络只能传输字节流API 调用、RPC 通信
持久化存储磁盘只能存储字节保存对象到文件、数据库
跨语言不同语言的数据结构不同Java 对象 → Python 字典
分布式缓存Redis/Memcached 存储字节缓存用户信息

2. 常见的序列化格式

👇 动手试试看:点击下方按钮,观察不同语言的序列化过程:

🔄序列化演示
📦内存对象
const user = {
  id: 123,
  name: "张三",
  email: "zhangsan@example.com",
  age: 28
};
内存中的对象,只能在当前进程使用
序列化
{}JSON 字符串68 bytes
{
  "id": 123,
  "name": "张三",
  "email": "zhangsan@example.com",
  "age": 28
}
可在网络传输、可跨语言
传输
💻二进制52 bytes
七进制编码 (MessagePack):
§ id 7b
¤ name £ 张三
¥ email ± zhangsan@example.com
£ age 1c
Protobuf/MessagePack,更小更快
📊 格式对比
格式
大小
速度
可读性
跨语言
JSON
★★★☆☆
★★★☆☆
★★★★★
★★★★★
XML
★★☆☆☆
★★☆☆☆
★★★★★
★★★★★
Protobuf
★★★★★
★★★★★
★☆☆☆☆
★★★★☆
MessagePack
★★★★☆
★★★★☆
★★☆☆☆
★★★★★

2.1 JSON:最通用

优点

  • 可读性好,调试方便
  • 所有语言都支持
  • 浏览器原生支持(JSON.parse / JSON.stringify

缺点

  • 体积大(有大量 {} "" 标记)
  • 不支持丰富的数据类型(Date、Map、Set 会被转换成字符串)

适用场景

  • 公开 API
  • 前后端通信
  • 配置文件

2.2 XML:曾经的主流

xml
<?xml version="1.0" encoding="UTF-8"?>
<user>
  <id>123</id>
  <name>张三</name>
  <email>zhangsan@example.com</email>
  <age>28</age>
</user>

优点

  • 结构清晰,支持注释
  • 支持复杂的嵌套结构
  • 有 Schema 验证(XSD)

缺点

  • 体积大,解析慢
  • 标签冗余(<open></close>

适用场景

  • 配置文件(Spring、MyBatis)
  • SOAP 协议
  • 复杂数据交换

2.3 Protobuf:最高效

protobuf
// user.proto
syntax = "proto3";
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
}

优点

  • 体积小(比 JSON 小 30-50%)
  • 速度快(解析速度快 5-10 倍)
  • 向后兼容(新增字段不影响老版本)

缺点

  • 不可读(二进制格式)
  • 需要 .proto 文件定义
  • 不支持动态类型

适用场景

  • 微服务内部通信
  • 高性能场景(游戏、实时通信)
  • 移动端 App(节省流量)

2.4 MessagePack:兼顾可读性和性能

json
// MessagePack 是 JSON 的二进制版本
// 相同数据,MessagePack 比 JSON 小 30% 左右

优点

  • 比 JSON 小,比 JSON 快
  • 保持 JSON 的数据模型
  • 支持所有 JSON 类型

缺点

  • 不可读
  • 不如 Protobuf 高效

适用场景

  • 需要性能但不想用 Protobuf
  • Redis 缓存
  • WebSocket 消息

3. 各语言序列化方式对比

语言JSON 库Protobuf 库XML 库
JavaScriptJSON.stringify()protobuf.jsfast-xml-parser
Pythonjson.dumps()protobufxmltodict
JavaJackson / Gsonprotobuf-javaJAXB
Goencoding/jsonprotoencoding/xml
C++nlohmann/jsonprotobuftinyxml2
C#System.Text.JsonGoogle.ProtobufSystem.Xml

💡 选择建议

  • 前后端通信:JSON(调试方便)
  • 微服务内部:Protobuf(性能最优)
  • 配置文件:JSON 或 YAML
  • 旧系统对接:XML(可能别无选择)

4. 性能对比

4.1 大小对比(以用户对象为例)

格式大小相对 JSON
JSON68 bytes100%
XML142 bytes209%
Protobuf38 bytes56%
MessagePack52 bytes76%

4.2 速度对比(序列化 10000 次)

格式耗时相对 JSON
JSON45 ms100%
XML120 ms267%
Protobuf8 ms18%
MessagePack28 ms62%

💡 性能测试结论

  • Protobuf 最快:适合高性能场景
  • MessagePack 次之:比 JSON 快 40% 左右
  • JSON 最慢:但对大多数场景已经足够

5. 常见问题

5.1 日期序列化问题

问题:Date 对象序列化后变成字符串

javascript
// 序列化前
const date = new Date('2024-01-01')

// 序列化后
JSON.stringify(date)  // "2024-01-01T00:00:00.000Z"

解决方案

javascript
// 方案1:转成时间戳
{ createdAt: date.getTime() }  // 1704067200000

// 方案2:转成 ISO 字符串
{ createdAt: date.toISOString() }  // "2024-01-01T00:00:00.000Z"

// 方案3:自定义序列化
JSON.stringify(obj, (key, value) => {
  if (value instanceof Date) {
    return { __type: 'Date', value: value.toISOString() }
  }
  return value
})

5.2 循环引用问题

问题:对象循环引用会报错

javascript
const obj = { name: 'test' }
obj.self = obj
JSON.stringify(obj)  // TypeError: Converting circular structure to JSON

解决方案

javascript
// 方案1:过滤掉循环引用
const seen = new WeakSet()
JSON.stringify(obj, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    if (seen.has(value)) return
    seen.add(value)
  }
  return value
})

// 方案2:使用 flatted 库
import { parse, stringify } from 'flatted'
stringify(obj)  // 自动处理循环引用

5.3 中文乱码问题

问题:中文序列化后乱码

原因

  • 字符编码不一致(UTF-8 vs GBK)
  • BOM 标记

解决方案

python
# Python 确保使用 UTF-8
import json
json.dumps(data, ensure_ascii=False)  # 不转义中文
javascript
// Node.js 设置响应头
res.setHeader('Content-Type', 'application/json; charset=utf-8')

6. 实战:电商系统序列化方案

6.1 场景分析

场景格式选择理由
App → 后端 APIJSON调试方便,前后端统一
后端 → 后端 RPCProtobuf性能最优,节省流量
缓存到 RedisMessagePack比 JSON 小,可序列化复杂对象
日志记录JSON便于日志分析工具解析

6.2 代码示例

javascript
// API 响应(JSON)
app.get('/api/products/:id', async (req, res) => {
  const product = await db.getProduct(req.params.id)
  res.json({
    code: 0,
    data: product
  })
})

// 微服务通信(Protobuf)
// product.proto
syntax = "proto3";
message Product {
  int32 id = 1;
  string name = 2;
  int32 price = 3;
}

// 服务端
const proto = require('./product.proto')
const message = proto.Product.create(product)
const buffer = proto.Product.encode(message).finish()

// 客户端
const decoded = proto.Product.decode(buffer)

// Redis 缓存(MessagePack)
const msgpack = require('msgpack-lite')
await redis.set(
  `product:${id}`,
  msgpack.encode(product)
)
const cached = msgpack.decode(await redis.get(`product:${id}`))

7. 用 AI 辅助选择序列化方案

AI 可以帮助你根据场景选择合适的序列化格式。

7.1 提示词模板

你是一位资深的系统架构师,精通数据序列化技术。请帮我选择合适的序列化方案。

## 业务场景
[描述你的场景,例如:电商 App、游戏后端、微服务等]

## 技术要求
[列出约束条件,例如:
- 前后端分离(Vue + Node.js)
- 性能要求高(QPS > 10000)
- 流量敏感(移动端,需节省流量)
- 需要跨语言(Java + Python + Go)]

## 数据特征
[描述数据特点,例如:
- 数据量大(单次响应 > 1MB)
- 结构复杂(多层嵌套)
- 包含日期、二进制数据等特殊类型]

## 输出格式
请按以下格式输出:
1. 推荐方案(JSON/Protobuf/MessagePack/XML)
2. 理由说明
3. 性能对比(估算大小和速度)
4. 代码示例

7.2 实战示例

输入提示词

你是一位资深的系统架构师。我们的电商 App 需要优化商品列表接口性能。

## 业务场景
- 电商 App 的商品列表接口
- 单次返回 100 个商品
- 每个 商品包含:id, name, price, image, description 等 20 个字段
- 当前使用 JSON,响应大小约 200KB,耗时 800ms

## 技术要求
- 前端是 Vue.js
- 后端是 Node.js + Go 微服务
- 需要兼顾性能和开发效率

## 优化目标
- 响应时间降到 300ms 以内
- 数据包大小减少 30%

AI 输出

推荐方案:混合方案

1. **API 层**:继续使用 JSON(前端友好)
2. **微服务通信**:切换到 Protobuf(性能提升)
3. **静态数据**:启用 gzip 压缩

### 理由说明
- JSON 对前端最友好,无需额外解析
- 后端微服务用 Protobuf,可以减少 40% 的数据量
- gzip 压缩对文本效果明显(JSON 可压缩 70%)

### 性能对比
| 方案 | 大小 | 耗时 |
|------|------|------|
| 当前 (JSON) | 200 KB | 800 ms |
| + gzip | 60 KB | 350 ms |
| + Protobuf | 50 KB | 280 ms |

### 代码示例
[具体实现代码...]

名词速查表

名词英文解释
序列化Serialization对象 → 字节流
反序列化Deserialization字节流 → 对象
JSONJavaScript Object Notation最常用的文本格式
XMLExtensible Markup Language标记语言,曾主流
ProtobufProtocol BuffersGoogle 开源的高效格式
MessagePack-JSON 的二进制版本
编码Encoding字符 → 字节
解码Decoding字节 → 字符