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

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
整数 / Integerpositive fixint0x00 – 0x7f正整数,1 字节
整数 / Integerfixmap0x80 – 0x8f最多 15 个键值对的映射
整数 / Integerfixarray0x90 – 0x9f最多 15 个元素的数组
整数 / Integerfixstr0xa0 – 0xbf最多 31 字节的字符串
整数 / Integernil0xc0空值
布尔 / Booleanfalse0xc2
布尔 / Booleantrue0xc3
二进制 / Binarybin 8/16/320xc4 – 0xc6原始字节
整数 / Integeruint 8/16/32/640xcc – 0xcf无符号整数
整数 / Integerint 8/16/32/640xd0 – 0xd3有符号整数
浮点 / Floatfloat 32/640xca – 0xcb浮点数
字符串 / Stringstr 8/16/320xd9 – 0xdb长字符串
数组 / Arrayarray 16/320xdc – 0xdd大数组
映射 / Mapmap 16/320xde – 0xdf大映射
扩展 / Extensionfixext 1/2/4/8/160xd4 – 0xd8定长扩展类型
扩展 / Extensionext 8/16/320xc7 – 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 80xcc10 ~ 255
uint 160xcd20 ~ 65,535
uint 320xce40 ~ 4,294,967,295
uint 640xcf80 ~ 18,446,744,073,709,551,615
int 80xd01-128 ~ 127
int 160xd12-32,768 ~ 32,767
int 320xd24-2,147,483,648 ~ 2,147,483,647
int 640xd38-2^63 ~ 2^63-1
值 300 的编码 (uint16):
  cd 01 2c    -- 0x012c = 300

值 70000 的编码 (uint32):
  ce 00 01 11 70  -- 0x00011170 = 70000

字符串变长格式

格式前缀字节长度字节最大长度
str 80xd91255 字节
str 160xda265,535 字节
str 320xdb44,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 80xc41255 字节
bin 160xc5265,535 字节
bin 320xc644,294,967,295 字节
字节数组 [0xDE, 0xAD, 0xBE, 0xEF]:
  c4 04       -- bin 8, 长度 4
  de ad be ef -- 数据

数组/映射变长格式

格式前缀字节长度字节最大元素数
array 160xdc265,535
array 320xdd44,294,967,295
map 160xde265,535
map 320xdf44,294,967,295

📖 浮点数格式 / Float Format

格式前缀字节字节数精度
float 320xca4IEEE 754 单精度
float 640xcb8IEEE 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 10xd41 字节
fixext 20xd52 字节
fixext 40xd64 字节
fixext 80xd78 字节
fixext 160xd816 字节

变长扩展格式

格式前缀字节长度字节数据长度
ext 80xc711–255 字节
ext 160xc821–65,535 字节
ext 320xc941–4,294,967,295 字节

预定义扩展类型

type 值名称用途
-1timestamp时间戳,自 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

编码含义
nil0xc0缺失/不存在
false0xc2布尔假
true0xc3布尔真

在某些语言中(如 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 大小节省
01 B1 B0%
422 B1 B50%
2563 B3 B0%
655365 B5 B0%
""2 B1 B50%
"a"3 B2 B33%
"hello"7 B6 B14%
[]2 B1 B50%
{}2 B1 B50%
null4 B1 B75%
true4 B1 B75%

典型业务数据压缩比

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
规范讨论 Issueshttps://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 进行序列化和反序列化。