MessagePack 序列化完全指南 / 02 - 格式规范 / Format Specification
格式规范 / Format Specification
本章深入讲解 MessagePack 的二进制编码规则、类型系统和数据格式。
This chapter dives deep into MessagePack’s binary encoding rules, type system, and data formats.
📖 类型系统概览 / Type System Overview
MessagePack 定义了以下数据类型,每种类型由一个**前缀字节(Prefix Byte)**标识:
值类型分类
| 类别 / Category | 类型 / Type | 前缀范围 / Prefix Range | 说明 / Description |
|---|---|---|---|
| 整数 / Integer | positive fixint | 0x00 – 0x7f | 正整数,1 字节 |
| 整数 / Integer | fixmap | 0x80 – 0x8f | 最多 15 个键值对的映射 |
| 整数 / Integer | fixarray | 0x90 – 0x9f | 最多 15 个元素的数组 |
| 整数 / Integer | fixstr | 0xa0 – 0xbf | 最多 31 字节的字符串 |
| 整数 / Integer | nil | 0xc0 | 空值 |
| 布尔 / Boolean | false | 0xc2 | 假 |
| 布尔 / Boolean | true | 0xc3 | 真 |
| 二进制 / Binary | bin 8/16/32 | 0xc4 – 0xc6 | 原始字节 |
| 整数 / Integer | uint 8/16/32/64 | 0xcc – 0xcf | 无符号整数 |
| 整数 / Integer | int 8/16/32/64 | 0xd0 – 0xd3 | 有符号整数 |
| 浮点 / Float | float 32/64 | 0xca – 0xcb | 浮点数 |
| 字符串 / String | str 8/16/32 | 0xd9 – 0xdb | 长字符串 |
| 数组 / Array | array 16/32 | 0xdc – 0xdd | 大数组 |
| 映射 / Map | map 16/32 | 0xde – 0xdf | 大映射 |
| 扩展 / Extension | fixext 1/2/4/8/16 | 0xd4 – 0xd8 | 定长扩展类型 |
| 扩展 / Extension | ext 8/16/32 | 0xc7 – 0xc9 | 变长扩展类型 |
📖 定长格式 / Fixnum Format
定长格式是 MessagePack 最高效的编码方式,将类型信息和数据压缩在最少的字节中。
正整数 (positive fixint)
前缀位 0xxxxxxx,值范围 0–127,仅需 1 字节:
值 42 的编码:
十进制: 42
二进制: 00101010
前缀: 0 (positive fixint 标志)
结果: 0x2a (1 字节)
对比 JSON: "42" = 0x34 0x32 (2 字节)
负整数 (negative fixint)
前缀位 111xxxxx,值范围 -32 到 -1,仅需 1 字节:
值 -1 的编码:
二进制: 11111111
结果: 0xff (1 字节)
值 -20 的编码:
二进制: 11101100
结果: 0xec (1 字节)
固定长度映射 (fixmap)
前缀位 1000xxxx,其中 xxxx 为键值对数量(0–15):
{"a": 1} 的编码:
81 -- fixmap, 1 个键值对
a1 61 -- fixstr, 长度 1, "a"
01 -- positive fixint, 1
总共: 3 字节 (JSON: 9 字节)
固定长度数组 (fixarray)
前缀位 1001xxxx,其中 xxxx 为元素数量(0–15):
[1, 2, 3] 的编码:
93 -- fixarray, 3 个元素
01 -- 1
02 -- 2
03 -- 3
总共: 4 字节 (JSON: 7 字节)
固定长度字符串 (fixstr)
前缀位 101xxxxx,其中 xxxxx 为字符串字节长度(0–31):
"hello" 的编码:
a5 -- fixstr, 长度 5
68 65 6c 6c 6f -- "hello"
总共: 6 字节 (JSON: 7 字节,含引号)
📖 变长格式 / Variable-length Format
当数据超出定长格式的容量时,使用变长格式。
整数变长格式
| 格式 | 前缀字节 | 数据字节 | 值范围 |
|---|---|---|---|
| uint 8 | 0xcc | 1 | 0 ~ 255 |
| uint 16 | 0xcd | 2 | 0 ~ 65,535 |
| uint 32 | 0xce | 4 | 0 ~ 4,294,967,295 |
| uint 64 | 0xcf | 8 | 0 ~ 18,446,744,073,709,551,615 |
| int 8 | 0xd0 | 1 | -128 ~ 127 |
| int 16 | 0xd1 | 2 | -32,768 ~ 32,767 |
| int 32 | 0xd2 | 4 | -2,147,483,648 ~ 2,147,483,647 |
| int 64 | 0xd3 | 8 | -2^63 ~ 2^63-1 |
值 300 的编码 (uint16):
cd 01 2c -- 0x012c = 300
值 70000 的编码 (uint32):
ce 00 01 11 70 -- 0x00011170 = 70000
字符串变长格式
| 格式 | 前缀字节 | 长度字节 | 最大长度 |
|---|---|---|---|
| str 8 | 0xd9 | 1 | 255 字节 |
| str 16 | 0xda | 2 | 65,535 字节 |
| str 32 | 0xdb | 4 | 4,294,967,295 字节 |
"hello world" 的编码:
ab -- fixstr, 长度 11 (11 <= 31,用 fixstr)
68 65 6c 6c 6f 20 77 6f 72 6c 64
长字符串 (256 字节):
d9 00 -- str 8, 长度字段
[256 字节数据]
二进制变长格式
| 格式 | 前缀字节 | 长度字节 | 最大长度 |
|---|---|---|---|
| bin 8 | 0xc4 | 1 | 255 字节 |
| bin 16 | 0xc5 | 2 | 65,535 字节 |
| bin 32 | 0xc6 | 4 | 4,294,967,295 字节 |
字节数组 [0xDE, 0xAD, 0xBE, 0xEF]:
c4 04 -- bin 8, 长度 4
de ad be ef -- 数据
数组/映射变长格式
| 格式 | 前缀字节 | 长度字节 | 最大元素数 |
|---|---|---|---|
| array 16 | 0xdc | 2 | 65,535 |
| array 32 | 0xdd | 4 | 4,294,967,295 |
| map 16 | 0xde | 2 | 65,535 |
| map 32 | 0xdf | 4 | 4,294,967,295 |
📖 浮点数格式 / Float Format
| 格式 | 前缀字节 | 字节数 | 精度 |
|---|---|---|---|
| float 32 | 0xca | 4 | IEEE 754 单精度 |
| float 64 | 0xcb | 8 | IEEE 754 双精度 |
3.14 的 float64 编码:
cb 40 09 1e b8 51 eb 85 1f
注意: MessagePack 使用大端序 (Big-Endian)
📖 扩展类型 / Extension Types
扩展类型允许用户自定义数据类型,是 MessagePack 的强大特性。
扩展类型结构
┌─────────────┬──────────────┬──────────────┐
│ 类型前缀 │ -1 ~ -128 │ 数据 (N字节) │
│ (1-3字节) │ (1字节) │ │
└─────────────┴──────────────┴──────────────┘
- type: 有符号整数,-1 到 -128 为预留给 MessagePack 的保留类型
- data: 用户自定义的字节序列
定长扩展格式
| 格式 | 前缀字节 | 数据长度 |
|---|---|---|
| fixext 1 | 0xd4 | 1 字节 |
| fixext 2 | 0xd5 | 2 字节 |
| fixext 4 | 0xd6 | 4 字节 |
| fixext 8 | 0xd7 | 8 字节 |
| fixext 16 | 0xd8 | 16 字节 |
变长扩展格式
| 格式 | 前缀字节 | 长度字节 | 数据长度 |
|---|---|---|---|
| ext 8 | 0xc7 | 1 | 1–255 字节 |
| ext 16 | 0xc8 | 2 | 1–65,535 字节 |
| ext 32 | 0xc9 | 4 | 1–4,294,967,295 字节 |
预定义扩展类型
| type 值 | 名称 | 用途 |
|---|---|---|
| -1 | timestamp | 时间戳,自 1970-01-01 UTC |
| 其他 | 用户自定义 | 应用自行定义 |
Timestamp 扩展详解
Timestamp 是 MessagePack 唯一预定义的扩展类型:
# 时间戳编码示例
import struct
import time
def encode_timestamp(ts_sec, ts_nsec=0):
"""编码 MessagePack timestamp"""
if ts_nsec == 0 and 0 <= ts_sec <= 0x3FFFFFFFF: # 34 位秒
data = struct.pack(">I", ts_sec)
return (4, -1, data)
elif 0 <= ts_sec <= 0x3FFFFFFFF: # 30 位秒 + 30 位纳秒
value = (ts_nsec << 34) | ts_sec
data = struct.pack(">Q", value)
return (8, -1, data)
else: # 64 位秒 + 32 位纳秒
data = struct.pack(">QI", ts_sec, ts_nsec)
return (12, -1, data)
# 2024-01-01 00:00:00 UTC = 1704067200 秒
length, ext_type, data = encode_timestamp(1704067200)
Timestamp 编码格式:
| 格式 | 数据长度 | 秒精度 | 纳秒精度 |
|---|---|---|---|
| 4 字节 | 34 位 | ✅ | ❌ |
| 8 字节 | 30 位 | ✅ | 30 位 |
| 12 字节 | 64 位 | ✅ | 32 位 |
📖 编码选择规则 / Encoding Selection Rules
MessagePack 编码器在序列化时应遵循以下规则选择最紧凑的格式:
整数编码选择
输入值 n:
if 0 <= n <= 127:
使用 positive fixint (1 字节)
elif -32 <= n <= -1:
使用 negative fixint (1 字节)
elif 0 <= n <= 255:
使用 uint 8 (2 字节)
elif 0 <= n <= 65535:
使用 uint 16 (3 字节)
elif -128 <= n <= 127:
使用 int 8 (2 字节)
elif -32768 <= n <= 32767:
使用 int 16 (3 字节)
elif 0 <= n <= 4294967295:
使用 uint 32 (5 字节)
elif -2147483648 <= n <= 2147483647:
使用 int 32 (5 字节)
elif 0 <= n <= 2^64-1:
使用 uint 64 (9 字节)
else:
使用 int 64 (9 字节)
字符串编码选择
字节长度 len:
if len <= 31:
使用 fixstr (1 + len 字节)
elif len <= 255:
使用 str 8 (2 + len 字节)
elif len <= 65535:
使用 str 16 (3 + len 字节)
else:
使用 str 32 (5 + len 字节)
💻 手动编码示例 / Manual Encoding Examples
编码 {"compact": true, "schema": 0}
逐步拆解:
原始数据: {"compact": true, "schema": 0}
Step 1: 映射头部
2 个键值对 → fixmap → 0x82
Step 2: 键 "compact"
7 字节 → fixstr → 0xa7
"compact" → 63 6f 6d 70 61 63 74
Step 3: 值 true
→ 0xc3
Step 4: 键 "schema"
6 字节 → fixstr → 0xa6
"schema" → 73 63 68 65 6d 61
Step 5: 值 0
0~127 → positive fixint → 0x00
最终结果:
82 a7 63 6f 6d 70 61 63 74 c3 a6 73 63 68 65 6d 61 00
共 18 字节 (JSON: 33 字节,节省 45%)
Python 验证
import msgpack
data = {"compact": True, "schema": 0}
packed = msgpack.packb(data)
# 验证编码
expected = bytes.fromhex("82a7636f6d70616374c3a6736368656d6100")
assert packed == expected
print(f"编码正确! {len(packed)} 字节 vs JSON {len(str(data))} 字符")
📖 字节序 / Byte Order
MessagePack 统一使用大端序(Big-Endian),也称为网络字节序。
值 0x1234 的存储:
大端序 (Big-Endian, MessagePack 使用):
地址: [0] [1]
数据: 0x12 0x34
小端序 (Little-Endian, x86 原生):
地址: [0] [1]
数据: 0x34 0x12
⚠️ 注意: 在小端序系统(如 x86)上编程时,库会自动处理字节序转换,无需手动干预。
📖 nil 与 false 的区别 / nil vs false
| 值 | 编码 | 含义 |
|---|---|---|
| nil | 0xc0 | 缺失/不存在 |
| false | 0xc2 | 布尔假 |
| true | 0xc3 | 布尔真 |
在某些语言中(如 Python),None 和 False 是不同的:
import msgpack
assert msgpack.packb(None) == b'\xc0'
assert msgpack.packb(False) == b'\xc2'
assert msgpack.packb(True) == b'\xc3'
# 反序列化时保持区分
assert msgpack.unpackb(b'\xc0') is None
assert msgpack.unpackb(b'\xc2') is False
assert msgpack.unpackb(b'\xc3') is True
💻 编码效率分析 / Encoding Efficiency Analysis
不同数据类型的编码效率
| 数据 | JSON 大小 | MsgPack 大小 | 节省 |
|---|---|---|---|
0 | 1 B | 1 B | 0% |
42 | 2 B | 1 B | 50% |
256 | 3 B | 3 B | 0% |
65536 | 5 B | 5 B | 0% |
"" | 2 B | 1 B | 50% |
"a" | 3 B | 2 B | 33% |
"hello" | 7 B | 6 B | 14% |
[] | 2 B | 1 B | 50% |
{} | 2 B | 1 B | 50% |
null | 4 B | 1 B | 75% |
true | 4 B | 1 B | 75% |
典型业务数据压缩比
import msgpack
import json
# 用户信息
user = {
"id": 12345,
"name": "张三",
"email": "[email protected]",
"age": 28,
"active": True,
"roles": ["admin", "editor"],
"address": {
"city": "北京",
"district": "朝阳区"
}
}
json_len = len(json.dumps(user, ensure_ascii=False).encode('utf-8'))
mp_len = len(msgpack.packb(user))
print(f"JSON: {json_len} B, MsgPack: {mp_len} B, 节省: {(1-mp_len/json_len)*100:.1f}%")
# JSON: 177 B, MsgPack: 119 B, 节省: 32.8%
⚠️ 注意事项 / Pitfalls
1. 整数溢出
不同语言处理大整数的方式不同:
import msgpack
# 超过 int64 范围的整数
big_num = 2**63 # 9223372036854775808
# 某些库可能报错或截断
try:
packed = msgpack.packb(big_num)
except OverflowError:
print("超出 int64 范围!")
2. 浮点精度丢失
import msgpack
# float32 精度有限
val = 3.14159265358979
packed_32 = msgpack.packb(val, use_single_float=True)
packed_64 = msgpack.packb(val)
# float32 版本精度较低
val_32 = msgpack.unpackb(packed_32)
val_64 = msgpack.unpackb(packed_64)
print(f"原始: {val}") # 3.14159265358979
print(f"float32: {val_32}") # 3.1415927410125732 (精度丢失)
print(f"float64: {val_64}") # 3.14159265358979
3. UTF-8 验证
部分库在反序列化时不验证 UTF-8 合法性,可能导致安全问题。
# 确保启用 UTF-8 验证
import msgpack
# 使用 strict_map_key 防止非字符串键
try:
msgpack.unpackb(b'\x81\x01\x01', strict_map_key=False)
except:
print("非法键类型!")
🔗 扩展阅读 / Further Reading
| 资源 | 链接 |
|---|---|
| MessagePack 规范原文 | https://github.com/msgpack/msgpack/blob/master/spec.md |
| 规范讨论 Issues | https://github.com/msgpack/msgpack/issues |
| Timestamp 扩展提案 | https://github.com/msgpack/msgpack/pull/203 |
| 二进制编码比较 | https://github.com/nicholasgasior/gophers-serialization-benchmarks |
📝 下一章 / Next: 第 3 章 - Python 实践 / Python Implementation — 在 Python 中使用 MessagePack 进行序列化和反序列化。