一、先搞懂:Protobuf 是什么 & 核心优势
Protobuf 是谷歌开源的结构化数据序列化协议(不是编程语言),核心是「先定义数据结构,再按固定规则编解码」,对比 JSON/XML 有 3 大核心优势:
- 体积更小:二进制编码,无冗余字符(比如 JSON 的
{}/:); - 速度更快:编解码无需解析字符串,直接操作二进制;
- 跨语言 / 跨平台:定义文件(.proto)可生成多语言代码(Java/Python/Go 等)。
二、编解码核心原理(10 分钟核心)
1. 前置:Protobuf 数据结构定义(基础)
先通过一个简单的 .proto 文件理解数据结构,这是编解码的「规则蓝本」:
syntax = "proto3"; // 指定版本(proto3 更简洁)
message User { // 定义一个「消息体」,类似编程语言的类/结构体
int32 id = 1; // 字段:类型 + 名称 + 字段编号(核心!编解码靠这个)
string name = 2;
bool is_vip = 3;
}
关键:字段编号(1/2/3)是编解码的核心标识,而非字段名(name/id),这是体积小的关键之一。
2. 编解码核心:「键值对」二进制编码规则
Protobuf 编码后的二进制数据,本质是「一系列键值对」,核心规则分 2 部分:
(1)Key 编码:Tag + Wire Type(标签 + wireType)
Key 是「字段编号 + 数据类型标识」的组合,占 1~5 个字节,计算规则:
- 公式:
Key = (字段编号 << 3) | WireType(左移 3 位,或上 wireType) - WireType:Protobuf 定义的「基础数据类型标识」,共 6 种核心类型,对应关系:
| WireType | 含义 | 对应 Protobuf 类型 |
|---|---|---|
| 0 | Varint(变长整数) | int32/int64/bool/enum/uint32 等 |
| 2 | Length-delimited | string/bytes/ 嵌套 message / 数组 |
| 1 | 64 位固定长度 | fixed64/uint64/double |
| 5 | 32 位固定长度 | fixed32/uint32/float |
👉 示例:User.id(字段编号 1,int32 对应 wireType0)Key = (1 << 3) | 0 = 8(二进制:1000)
(2)Value 编码:按 WireType 分规则
这是编解码的核心,重点讲 2 种最常用的 WireType:
① WireType 0:Varint(变长整数,核心中的核心)
- 作用:用「1~10 个字节」表示整数,小数字占更少字节(比如 123 占 1 字节,100000 占 2 字节);
- 编码规则:
- 每个字节的最高位(第 8 位) 是「续行位」:1 表示后面还有字节,0 表示最后一个字节;
- 每个字节的低 7 位 存储数字的实际二进制(小端序:先存低位,后存高位)。
- 示例:编码数字 150
- 150 的二进制:10010110
- 拆分低 7 位:0010110(第一字节) + 1(第二字节)
- 第一字节:0010110 | 10000000 = 1010110(十进制 174)
- 第二字节:1 | 00000000 = 00000001(十进制 1)
- 最终编码:0x96 0x01(十六进制),仅占 2 字节。
② WireType 2:Length-delimited(长度前缀型)
- 作用:编码「长度不固定」的数据(字符串 / 嵌套对象 / 数组);
- 编码规则:
- 先存「数据长度」(按 Varint 编码);
- 再存「数据的原始二进制」(比如字符串转 UTF-8 二进制)。
- 示例:编码 User.name = "Tom"
- 字段编号 2,string 对应 wireType2 → Key = (2 <<3)|2 = 18(0x12);
- "Tom" 的 UTF-8 二进制:0x54 0x6F 0x6D,长度 = 3;
- 长度 3 按 Varint 编码是 0x03;
- 最终 Value 编码:0x03 0x54 0x6F 0x6D;
- 整个键值对:0x12 0x03 0x54 0x6F 0x6D。
3. 完整编解码示例(把 User 序列化)
假设 User 数据:id=150,name="Tom",is_vip=true(true 在 Protobuf 中是 1)
编码过程(序列化):
- 编码 id=150:
- Key:(1<<3)|0 = 8(0x08);
- Value:150 的 Varint 编码 0x96 0x01;
- 这部分二进制:0x08 0x96 0x01。
- 编码 name="Tom":
- Key:(2<<3)|2 = 18(0x12);
- Value:0x03 0x54 0x6F 0x6D;
- 这部分二进制:0x12 0x03 0x54 0x6F 0x6D。
- 编码 is_vip=true:
- Key:(3<<3)|0 = 24(0x18);
- Value:1 的 Varint 编码 0x01;
- 这部分二进制:0x18 0x01。
- 最终二进制:0x08 0x96 0x01 0x12 0x03 0x54 0x6F 0x6D 0x18 0x01(仅 9 字节!)。
解码过程(反序列化):
- 读第一个字节 0x08 → Key=8;
- 解析 Key:8 >> 3 = 1(字段编号),8 & 0x07 = 0(wireType0);
- 按 wireType0 读后续字节,解析出 Value=150 → 赋值给 id;
- 继续读下一个字节 0x12 → Key=18 → 字段编号 2,wireType2;
- 按 wireType2 先读长度(0x03),再读 3 个字节 → 解析为 "Tom" → 赋值给 name;
- 重复上述步骤,解析出 is_vip=true;
- 最终还原出完整的 User 对象。
三、核心补充(1 分钟快速记)
- 版本差异:proto2 支持「字段默认值 / 可选 / 必填」,proto3 更简洁(默认所有字段可选,无必填);
- 兼容性:字段编号一旦发布不要改!新增字段用新编号,老版本解析时会忽略未知字段,保证向前兼容;
- 为什么快:二进制 + 固定规则 + 无冗余解析(无需像 JSON 那样解析字符串)。
总结
- Protobuf 编解码核心是「按字段编号 + wireType 编 Key,按 wireType 规则编 Value」,二进制键值对结构是基础;
- Varint(变长整数)是最核心的编码规则,通过「续行位 + 低 7 位存数据」实现小数字省空间;
- Length-delimited 用于字符串 / 嵌套对象,规则是「先存长度(Varint),再存数据」。
这就是 Protobuf 编解码的核心原理,10 分钟掌握这些,就能理解它为什么体积小、速度快
2704

被折叠的 条评论
为什么被折叠?



