强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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),NoneFalse 是不同的:

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": "zhangsan@example.com",
    "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 进行序列化和反序列化。