Protobuf 理解成一种「高效的结构化数据格式」,专门用来在不同程序之间传递或存储数据。它和 JSON、XML 的作用类似,但更小巧、更快。
为什么需要 Protobuf?
假设你要在两个程序之间传递一个「用户信息」,包含姓名、年龄、邮箱。
用 JSON 可能是这样:
{"name":"小明","age":18,"email":"xiaoming@test.com"}
用 Protobuf 存储同样的信息,会变成二进制数据(类似 \x08\x12\x06\xe5\xb0\x8f\xe6\x98\x8e...),虽然人看不懂,但程序能高效处理,而且体积更小。
Protobuf 的核心:.proto
文件
Protobuf 不直接写数据,而是先定义「数据结构规则」,这个规则就写在 .proto 文件里。
比如上面的用户信息,规则文件(user.proto)长这样:
syntax = "proto3"; // 声明使用第3版语法(最新版)
// 定义一个"用户"数据结构
message User {
string name = 1; // 姓名(string类型)
int32 age = 2; // 年龄(整数类型)
string email = 3; // 邮箱(string类型)
}
这里的 1、2、3
是「字段编号」,不是值!作用是:
在二进制编码时标识字段(比用字段名更省空间)
后续添加新字段时,老程序仍能兼容(只要不删除 / 修改已有编号)
特点 | JSON | Protobuf |
---|---|---|
格式 | 文本(人能看懂) | 二进制(人看不懂) |
体积 | 较大(包含字段名等) | 小(用编号代替字段名) |
速度 | 解析较慢 | 解析极快 |
类型检查 | 弱(运行时可能出错) | 强(编译时就报错) |
兼容性 | 需手动处理版本兼容 | 天然支持向后兼容 |
程序 A 要给程序 B 发送一个 "用户信息"(姓名、年龄、邮箱)
序列化:程序 A 把这些信息按 Protobuf 规则转换成二进制数据(像打包好的箱子)
传输:二进制数据通过网络发给程序 B(像快递运输)
反序列化:程序 B 收到二进制数据,按同样的规则还原成 "姓名、年龄、邮箱"(像拆箱取东西)
安装 protoc 编译器
浏览器直达 GitHub Releases:
Releases · protocolbuffers/protobuf · GitHub
选 protoc-28.2-win64.zip
D:\protoc-28.2\bin 追加到系统 PATH
cmd输入
protoc --version
然后安装protobuf库
pip install protobuf
创建 Protobuf tobuf 规则
在任意文件夹(比如 D:\protobuf-test
)中,新建一个 student.proto
文件,用记事本或 VS Code 打开,写入以下内容:
syntax = "proto3"; // 使用第 3 版语法(必须写在第一行)
// 定义一个「学生」数据结构
message Student {
string name = 1; // 姓名(编号 1)
int32 age = 2; // 年龄(编号 2)
string school = 3; // 学校(编号 3)
float score = 4; // 分数(编号 4)
}
message Student 相当于 Python 中的 class Student,用来定义数据结构
每个字段格式:类型 字段名 = 编号(编号是 1-15 的数字,不能重复,用于二进制编码)
生成 Python 代码(自动工具)
打开命令提示符(Win+R 输入 cmd
),进入上面的文件夹:
cd D:\protobuf-test
输入以下命令,用编译器把 .proto
文件转成 Python 代码:
protoc --python_out=. student.proto
执行后,文件夹里会多出一个 student_pb2.py
文件,这个文件是自动生成的,不用修改,里面包含了操作 Student
数据的所有工具。
编写 Python 代码使用 Protobuf
在同一文件夹中,新建 main.py
文件,写入以下代码:
import student_pb2 # 导入自动生成的代码
def main():
# 1. 创建一个学生对象并赋值
student = student_pb2.Student()
student.name = "小明"
student.age = 15
student.school = "阳光中学"
student.score = 95.5
print("原始数据:")
print(f"姓名:{student.name}")
print(f"年龄:{student.age}")
print(f"学校:{student.school}")
print(f"分数:{student.score}\n")
# 2. 序列化:把对象转成二进制(可用于网络传输或存储)
serialized_data = student.SerializeToString()
print(f"序列化后的二进制长度:{len(serialized_data)} 字节")
print(f"二进制数据(十六进制):{serialized_data.hex()}\n")
# 3. 反序列化:把二进制转回对象
new_student = student_pb2.Student()
new_student.ParseFromString(serialized_data)
print("反序列化后的数据:")
print(f"姓名:{new_student.name}")
print(f"年龄:{new_student.age}")
print(f"学校:{new_student.school}")
print(f"分数:{new_student.score}")
if __name__ == "__main__":
main()
对比维度 | XML | Protobuf | JSON |
---|---|---|---|
数据格式 | 标签式文本格式(如 <name>小明</name> ) |
二进制格式(不可读,需工具解析) | 键值对文本格式(如 {"name": "小明"} ) |
可读性 | 较高(文本格式,结构清晰) | 极低(二进制,人类无法直接阅读) | 高(文本格式,结构简洁) |
序列化 / 反序列化速度 | 较慢(解析标签开销大) | 极快(二进制编码,解析效率高) | 较快(文本解析,效率低于 Protobuf) |
数据体积 | 较大(标签冗余多) | 极小(二进制压缩,无冗余) | 中等(键名重复,比 XML 紧凑) |
类型安全 | 弱(需额外定义 Schema 校验) | 强(依赖 .proto 文件严格定义类型) |
弱(默认无类型,需额外校验) |
扩展性 | 较好(可通过命名空间、标签扩展) | 极佳(支持字段增删,兼容旧版本) | 较好(键值对灵活,但无官方扩展机制) |
跨语言支持 | 广泛支持(几乎所有语言) | 广泛支持(官方提供多语言生成工具) | 广泛支持(所有主流语言内置 / 库支持) |
Schema 定义 | 可选(需通过 XSD 单独定义) | 必须(通过 .proto 文件强制定义) |
可选(可通过 JSON Schema 定义) |
适用场景 | 配置文件、文档标记(如 HTML 基于 XML) | 高性能通信(如 RPC、游戏协议) | 前后端交互、API 接口(如 RESTful) |
学习成本 | 中等(需学习标签语法和 XSD) | 中等(需学习 .proto 语法) |
低(语法简单直观) |
123