10分钟,掌握Protobuf编解码原理

引言

本文介绍了 Protobuf 的基本概念和编解码原理。原理部分使用了直观的表格解析编码过程,帮助开发者在快速掌握核心机制。

一、Protobuf 介绍

Protobuf 全称 Protocol Buffer(后文简称 PB),是由 Google 开发的一种语言和平台无关的数据序列化协议。

它能将结构化数据序列化为更简洁的二进制格式,用于数据存储和网络通信。

与 JSON 或 XML 相比,PB 更高效,但是会牺牲可读性。

使用 PB 前,必须预先定义好数据结构,然后通过工具生成对应语言的代码来读写数据。

官网链接:protobuf.dev/

💡预定义结构示意:

使用 PB 的第一步,就是需要预先定义好你的数据结构。创建一个 .proto 文件。在这个文件里,你需要明确描述数据的结构,比如有哪些字段,它们是什么类型。

ini 复制代码
syntax = "proto3";

message Account {
  int32 id = 1;
  string name = 2;
  string email = 3;
  AccountType accountType = 4;

  enum AccountType {
    TYPE_1 = 0;
    TYPE_2 = 1;
    TYPE_3 = 2;
    TYPE_4 = 3;
  }
}

需要注意的是,在 proto3 中,不能显式定义自定义默认值 ,字段后面的数字为字段的编号,并非默认值,编号不能重复。

更多编写规范可以参考官方文档: protobuf.dev/programming...

💡生成平台代码示意:

定义好结构之后,Protobuf 提供的编译器工具就能根据这个 .proto 文件,自动生成你所需编程语言的代码。这些生成的代码包含了用于读写你定义的数据结构的方法。下面以 Kotlin 使用 wire 为例:

kotlin 复制代码
public class Account(
  @field:WireField(tag = 1,adapter = "com.squareup.wire.ProtoAdapter#INT32",label = WireField.Label.OMIT_IDENTITY,schemaIndex = 0,)
  public val id: Int = 0,
  @field:WireField(tag = 2,adapter = "com.squareup.wire.ProtoAdapter#STRING",label = WireField.Label.OMIT_IDENTITY,schemaIndex = 1,)
  public val name: String = "",
  @field:WireField(tag = 3,adapter = "com.squareup.wire.ProtoAdapter#STRING",label = WireField.Label.OMIT_IDENTITY,schemaIndex = 2,)
  public val email: String = "",
  @field:WireField(tag = 4,adapter = "com.example.mykuikly.proto.Account${'$'}AccountType#ADAPTER",label = WireField.Label.OMIT_IDENTITY,schemaIndex = 3,)
  public val accountType: AccountType = AccountType.TYPE_1,
  unknownFields: ByteString = ByteString.EMPTY,
) : Message<Account, Nothing>(ADAPTER, unknownFields) {

public companion object {
    @JvmField
    public val ADAPTER: ProtoAdapter<KAccount> = object : ProtoAdapter<KAccount>(
      FieldEncoding.LENGTH_DELIMITED, 
      KAccount::class, 
      "type.googleapis.com/com.netease.mail.mmsharedkmp.proto.KAccount", 
      PROTO_3, 
      null, 
      "com/netease/mail/mmsharedkmp/proto/account.proto"
    ) {
      override fun encodedSize(`value`: KAccount): Int {//省略}

      override fun encode(writer: ProtoWriter, `value`: KAccount) {//省略}

      override fun encode(writer: ReverseProtoWriter, `value`: KAccount) {//省略}

      override fun decode(reader: ProtoReader): KAccount {//省略}

      override fun redact(`value`: KAccount): KAccount = //省略
    }
  }
  //其他部分省略
}

可以看到,工具帮我们自动生成了.proto 中定义的数据结构,包含了字段和用来编码解码的方法。

💡编码后数据示意

如上文所说,PB 会将我们的结构化数据序列化成一种非常紧凑的二进制格式。但是可读性会很差:

可以看到,PB 编码后已经无法直接看出内部的数据含义了。

二、编解码原理

我们以上面定义的数据结构为例,PB 编码后数据是一个紧凑的二进制格式,他的字节数组十进制表示如下:

PB 编码后数据 [ 8, 1, 18, 4, 110, 97, 109, 101, 26, 12, 116, 101, 115, 116, 64, 49, 54, 51, 46, 99, 111, 109 ](22 字节)

💡 PB 编码过程解析

为了方便演示,我将每个十进制表示的字节,转换成二进制,放在表的第一列;

第二列为 每个字节解析出的内容,以及解释;

为了参考对照,第三列附了 ASCII 码表。

阅读时建议从上到下,左右对照。

💡 PB 编码的优化策略

通过前面的分析,我们得到了 PB 压缩后的数据内容,如下:

perl 复制代码
{
    "1" : 1,
    "2" : "name" ,
    "3" : "test@163.com"
}

从这份数据中,我们可以观察到两个值得注意的现象:

  1. 数据中没有出现字段名

  2. accountType 字段似乎不见了

这主要是因为 PB 在序列化时的优化策略:

具体来说,PB 不需要存储字段名,因为字段名已经在生成的代码中定义好了,通过 ID 就能直接映射到对应的字段,这样避免了冗余信息。

另外,PB 在压缩过程中会省略值为默认值的字段;我们传递的 accountType 值为 TYPE_1,其数值为 0,恰好是默认值,因此它没有被包含在输出数据中。

三、总结

总结 Protobuf 编解码原理,它通过精简数据表示和省略冗余信息,显著提升性能。Protobuf 是一种高效实用的序列化工具,在开发中能大幅优化资源利用。

相关推荐
程序员鱼皮1 天前
刚刚,Claude Opus 4.6 和 GPT-5.3-Codex 同时炸场!AI 编程要变天了
计算机·ai·程序员·互联网·软件开发
Stephen_Young2 天前
32岁程序员猝死:让我想起了我曾经的加班经历,庆幸自己还活着
程序员·工控
良许Linux2 天前
51单片机都有哪些优缺点
单片机·程序员·嵌入式·编程
程序员鱼皮4 天前
前特斯拉 AI 总监:AI 编程最大的谎言,是 “提效”
前端·后端·ai·程序员·开发
阿里嘎多学长4 天前
2026-02-02 GitHub 热点项目精选
开发语言·程序员·github·代码托管
良许Linux5 天前
DSP的选型和应用
后端·stm32·单片机·程序员·嵌入式
淘源码d7 天前
【开源可商用】高并发智慧校园SaaS平台核心源码:Spring Boot 微服务 + 多终端协同
java·程序员·智慧校园·源码·二次开发·软件源码·电子班牌系统
程序员鱼皮8 天前
7个神级技巧,彻底去除网站的 AI 味儿!
计算机·ai·程序员·互联网·网站·编程经验
程序员鱼皮11 天前
Agent Skills 傻瓜式教程,26 年最火 AI 技术就这?
计算机·ai·程序员·agent·编程经验
黑客-雨13 天前
DeepSeek-V3.2深度拆解:开源模型逆袭,GPT-5迎来劲敌!
人工智能·程序员·大模型·知识图谱·agent·大模型教程·deepseek-v3.2