文章目录
一、消息结构
【结论先行】:
- protobuf将消息里的每个字段进行编码后,再利用T-L-V或者T-V的方式进行数据存储。
- protobuf对于不同类型的数据会使用不同的编码和存储方式。
- protobuf的编码和存储方式是其性能优越、数据体积小的原因。
protobuf中每一个消息中的字段都是key-value类型,序列化的时候key-value被编码和存储为字节流,整体的消息结构如下图所示:
Tag 由字段编号 field_number 和 编码类型 wire_type 组成, Tag 整体采用 Varints 编码。Tag的值为(field_number << 3) | wire_type,即最后三位存储wire type(编码类型),其他位用于存储field number(字段编号)。
wire type编码有以下几种类型:
【T-L-V格式】:
-
T 作为标识号表明了在message中的位置以及数据类型。
-
L 定义了value的长度,表示可变长度的字段。
-
V 表示实际的value。
二、Wire Type = 0时的编码和存储方式
【结论先行】:对于int32/int64类型的数据(正数),protobuf会使用Varints编码;而对于sint32/sint64类型的数据(负数),protobuf会先使用ZigZag 编码,再使用Varints编码。存储格式为Tag-Value。
2.1 编码方式
2.1.1 Varints编码
Varints是使用一个或多个字节序列化整数的一种方法,也是一种压缩算法。在Varints编码方式中,数字越小,占用的字节越少。Varints编码的主要依据是:越小的数字出现的频率越高。
通常我们要存储一个整数需要4个字节或者8个字节,例如对于32位整数300,其二进制表示为:00000000 00000000 00000001 00101100
。可以看到二进制表示中有很多无用的0也占用了内存,因此可以使用Varints编码对其进行压缩和解码。
Varints编码使用msb表示当前是否是最后一个字节,如果msb为1表示后面还有字节,如果msb为0表示当前字节是最后一个字节。因为加入了msb,所以每一个字节低七位表示具体的数值。
【Varints编码过程如下】:
- 先将整数转换为不带前导0的二进制表示:
100101100
。 - 每一个字节的低七位存储数值:
0000010 0101100
。 - 转换为小端模式:
0101100 0000010
。 - 添加msb标志位:
10101100 00000010
。
从上述编码过程可知,原来需要四个字节表示的整数经过编码后只需要两个字节即可存储。Varints编码由于使用了msb标志位,因此可以表示的最大整数为2^28,所以说Varints编码基本可以满足绝大多数的应用场景。
2.1.2 ZigZag编码
Varints的不足在于对负数进行编码时效率极低,因为负数的二进制表示中1特别多,所以没办法去掉前导0,所以使用Varints编码意义不大。ZigZag编码被用来解决这一问题,其核心思想是将有符号整数转换为无符号整数,进而能够使用Varints编码。如下图所示:
以负数-11为例,其二进制在计算机中是用补码表示的,整数原码为:00001011
,反码为:11110100
,补码(反码加1)为:11110101
。
【ZigZag编码过程如下】:
- 原数左移一位(左移时丢弃最高位,低位补0):
11101010
。 - 原数右移31位(右移时符号位不变,高位补符号位):
11111111
。 - 将上述两个二进制数取异或:
00010101
。
ZigZag编码和解码过程用代码表示为:
int int_to_zigzag(int n)
{
return (n <<1) ^ (n >>31);
}
int zigzag_to_int(int n)
{
return (((unsignedint)n) >>1) ^ -(n & 1);
}
2.2 存储方式
protobuf使用Varints和ZigZag编码后,以T-V格式存储数据,即:
三、Wire Type = 2时的编码和存储方式
【结论先行】:对于string,bytes和嵌套消息类型的数据,protobuf会使用Length-delimited编码,存储格式为Tag-Length-Value。
3.1 编码方式
当时value的类型为string,bytes和嵌套消息时,protobuf使用Length-delimited编码,即将value的length也编码进最终数据。
例如:
message Test2 {
required string b = 2;
}
// 设置b的值为"testing",则编码之后为:
12 07 74 65 73 74 69 6e 67
3.2 存储方式
使用Length-delimited编码时的数据存储方式为T-L-V,即:
四、Wire Type = 1&5时的编码和存储方式
【结论先行】:对于大整数类型的数据,protobuf会使用64-bit和32-bit编码方式,存储格式为Tag-Value。
4.1 编码方式
Varints适合处理一定范围内的数字,当数字很大的时候使用Varints编码效率反而很低,因此protobuf定义了64-bit和32-bit两种定长编码类型,即:
4.2 存储方式
protobuf使用64-bit和32-bit编码后,以T-V格式存储数据。
参考:
https://developers.google.cn/protocol-buffers/docs/encoding
https://www.bilibili.com/video/BV1Bt411X73L/?spm_id_from=333.788.videocard.19
https://www.cnblogs.com/mler/p/10252886.html
https://www.jianshu.com/p/30ef9b3780d9
https://www.jianshu.com/p/6dd2fd0362b8