ProtoBuf 编码原理
FreeGuideOnline
最新
2026-07-10
Tag = (field_number << 3) | wire_type
- **field_number**:就是 `.proto` 文件中定义的字段编号,如 `int32 age = 1;` 中的 `1`。
- **wire_type**:3 位宽,用于指明该字段值部分采用哪种编码格式。ProtoBuf 定义了 6 种 wire type:
| wire_type | 名称 | 用于 |
|-----------|----------------------|--------------------------------------------------------------|
| 0 | Varint | int32, int64, uint32, uint64, bool, enum, sint32, sint64 等 |
| 1 | 64-bit | fixed64, sfixed64, double |
| 2 | Length-delimited | string, bytes, 嵌套消息, packed repeated 字段 |
| 3 | Start group | 已废弃(groups 类型) |
| 4 | End group | 已废弃(groups 类型) |
| 5 | 32-bit | fixed32, sfixed32, float |
**示例**:字段 `int32 age = 1;` 的 tag 计算:
- field_number = 1,左移 3 位得 8(二进制 `1000`)
- wire_type = 0(Varint)
- Tag = `8 | 0` = 8。该字段在二进制流中第一个字节即为 `08`。
## 3. Varint 编码:整数基础
Varint(可变长整数)是理解 ProtoBuf 编码的基石。它使用一种巧妙的方案:**每个字节用最高位(msb)作为延续标志,剩余 7 位存储数值,小端序排列**。
**规则**:
- 一个字节的 msb 为 1 表示后面还有字节,为 0 表示这是最后一个字节。
- 有效数据位按小端序组合(第一个字节是最低 7 位)。
**示例:编码数值 300**
1. `300` 的二进制是 `100101100`。
2. 拆成 7 位一组(从低位开始):`0101100` 和 `0000010`。
3. 小端序排列:先写低位组 `0101100`,再写高位组 `0000010`。
4. 为最后一个字节添加 msb = 0,其余 msb = 1:
- 第一字节:`1 0101100` = `AC`
- 第二字节:`0 0000010` = `02`
5. 最终编码:`AC 02`。
**为什么要这样做**?因为实际数据中绝大多数整数都很小(如年龄、数量),用 Varint 编码可以省去高位全零的字节,显著压缩体积。
## 4. ZigZag 编码:让负数变小
Varint 对负数处理极差:因为所有负整数在计算机中都是用补码表示,最高位始终为 1,导致 Varint 会占用满 10 字节(对于 int64)。为解决这一问题,ProtoBuf 引入了 `sint32` 和 `sint64` 类型,它们内部使用 ZigZag 编码。
**ZigZag 算法**:将有符号整数映射为无符号整数,使得绝对值小的负数映射后依然很小。
- 对于 int32:`(n << 1) ^ (n >> 31)` (右移为算术右移)
- 对于 int64:`(n << 1) ^ (n >> 63)`
**示例**:
- `0` 映射后 `0` → Varint `00`
- `-1` 映射后 `1` → Varint `01`
- `1` 映射后 `2` → Varint `02`
- `-2` 映射后 `3` → Varint `03`
编码过程:原数值 → ZigZag 转换为无符号 → Varint 编码写入。这样负数的空间开销与正数一致。如果你经常使用负数,请务必选择 sint 系列。
## 5. 定长编码 (64-bit / 32-bit)
某些数据类型不需要可变长度,直接按照固定字节数编码更简单高效:
- **64-bit(wire type 1)**:fixed64, sfixed64, double
直接小端序写入 8 个字节。
- **32-bit(wire type 5)**:fixed32, sfixed32, float
直接小端序写入 4 个字节。
为什么还有 fixed32/fixed64?当数值范围较大且分布均匀时(如哈希值、传感器原始数据),定长编码比 Varint 更快,因为无需进行位运算解码。
## 6. Length-delimited 编码(长度分隔)
wire type 2 涵盖了字符串、字节数组、嵌套消息,以及 packed repeated 字段。其格式为:
Tag (包含 wire type=2) + Length (Varint编码) + 实际数据