51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Google Protobuf 编码

Protocol Buffer 会将对象序列化为二进制数据。在本文中,我们简单了解下它是如何对数据进行编码的,即:了解下底层的编码格式。当然并非必须的,日常使用 Protocol Buffer 不需要精通这些细节。不过,对于想要进行性能优化的人来说,理解底层格式会很有帮助。

Free Hex Editor Neo:https://freehexeditorneo.com/

  1. 准备数据 {#title-0} ==================

在项目中创建 encode.proto 文件:

syntax = "proto3";

message MyVarint { int32 num1 = 1; int64 num2 = 2; bool num3 = 3; }

message MyFixed { fixed32 num1 = 1; fixed64 num2 = 2; }

message MyLen { string num1 = 1; }

message MyCustom { repeated int32 num1 = 1; map<int32, string> num2 = 2; }

编译生成 C++ 操作接口,并编写如下代码生成二进制编码文件:

#if 1

#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<fstream> using namespace std;

#include "encode.pb.h"

void test01() { MyVarint data; data.set_num1(214748); data.set_num2(20); data.set_num3(true);

ofstream ofs(&quot;my-varint.bin&quot;, ios::binary);
data.SerializeToOstream(&amp;ofs);
ofs.close();

}

void test02() { MyFixed data; data.set_num1(10); data.set_num2(20);

ofstream ofs(&quot;my-fixed.bin&quot;, ios::binary);
data.SerializeToOstream(&amp;ofs);
ofs.close();

}

void test03() { MyLen data; data.set_num1("abc");

ofstream ofs(&quot;my-len.bin&quot;, ios::binary);
data.SerializeToOstream(&amp;ofs);
ofs.close();

}

void test04() { MyCustom data; data.add_num1(10); data.add_num1(20);

data.mutable_num2()-&gt;insert({1, &quot;aaa&quot;});
data.mutable_num2()-&gt;insert({2, &quot;bbbb&quot;});

ofstream ofs(&quot;my-custom.bin&quot;, ios::binary); data.SerializeToOstream(&amp;ofs); ofs.close();

}

int main() { test01(); test02(); test03(); test04();

return EXIT_SUCCESS;

} #endif

  1. 编码探究 {#title-1} ==================

message 中的每一个字段都会使用一个字节来进行标识:

  • 前 3 个二进制位表示编码类型
  • 后 5 个二进制位表示字段编号

不同的字段类型对应着不同的编码方式,下表展示了不同的类型与编码方式的对应关系:

接下来,我们将会探讨不同的存储方式。

2.1 VARINT {#title-2}

VARINT 使用可变字节数来存储整数,具体使用多少个字节取决于整数的大小。小的整数会占用更少的字节,而大的整数则需要更多的字节。

syntax = "proto3";

message MyVarint { int32 num1 = 1; int64 num2 = 2; bool num3 = 3; }

假设我们实例化的对象:

num1: 214748
num2: 20
num3: true

序列化之后的二进制数据:

# num1 编码
00001000 11011100 10001101 00001101 
# num2 编码
00010000 00010100 
# num3 编码
00011000 00000001

2.2 I64 和 I32 {#title-3}

I32I64 是用于表示整数的数据类型。它们分别代表 32 位和 64 位有符号整数。这些数据使用 4 和 8 字节进行数据存储。

syntax = "proto3";

message MyFixed { fixed32 num1 = 1; fixed64 num2 = 2; }

假设我们实例化的对象:

num1: 10
num2: 20

序列化之后的二进制数据:

# num1 编码
00001101 00001010 00000000 00000000 00000000 
# num2 编码
00010001 00010100 00000000 00000000 00000000 00000000 00000000 00000000 00000000

2.3 LEN {#title-4}

在序列化时,首先编码数据的长度,然后是实际的数据。

syntax = "proto3";

message MyLen { string num1 = 1; }

假设我们实例化的对象:

num1: "abc"

序列化之后的二进制数据:

# num1 编码
00001010 00000011 01100001 01100010 01100011

2.4 复杂类型 {#title-5}

使用长度+数据的方式进行存储。

syntax = "proto3";

message MyCustom { repeated int32 num1 = 1; map<int32, string> num2 = 2; }

假设我们实例化的对象:

num1: 10
num1: 20

num2 { key: 2 value: "bbbb" }

num2 { key: 1 value: "aaa" }

序列化之后的二进制数据:

# num1 编码
00001010 00000010 00001010 00010100 
# num2 {2, "bbbb"} 编码
00010010 00001000 00001000 00000010 00010010 00000100 01100010 01100010 01100010 01100010 
# num2 {1, "aaa"} 编码
00010010 00000111 00001000 00000001 00010010 00000011 01100001 01100001 01100001

至此,我们能够大概了解 Proto Buffer 是如何进行数据序列化。


赞(3)
未经允许不得转载:工具盒子 » Google Protobuf 编码