Home protobuf encoding 解析
Post
Cancel

protobuf encoding 解析

Protobuf是谷歌推出的跨语言,跨平台,可扩展的数据序列化机制,相比XML,JSON等序列化方式,它具有更简单,更快,更小的优点,其中体积更小的优点得益于它的encoding方式。

Protobuf 消息是一种key-value形式的结构,如下图所示

1

其中Key包含了field_number和wire_type,计算公式:

1
key = (field_number << 3) | wire_type

wire_type就是protobuf的编码方式,可用的wire_type和pb对应的数据类型如下:

TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float

Base 128 Varints

Varint(可变长int类型)使用一个或者多个byte序列化int类型的方法,数字越小,需要的byte越少,类似sql的varchar。

Varint 中的每个字节(最后一个字节除外)都设置了最高有效位(msb),用来表示后续还有没有byte数据,剩下的7bits用来存储数据的二进制补码(two’s complement),因为protobuf采用小端序(little-endian byte order)编码,先读到的是低位数据,所有一个数据如果有多个byte,解析时需要反转一下顺序。

看一个例子:

1
2
3
message Test1 {
  optional int32 a = 1;
}

如果将a设置为150,序列化之后的结果就是 08 96 01 ,解析时先读到一个字节08 ,二进制0000 1000,msb位是0,后续没有数据,因此第一个字节就是key,去除msb后 000 1000, 低位3bit表示wire_type,是0,所以后面的value采用varint编码,右移三位后,剩下的就是field_number,为1。

解析完key后,再去解析value,一直读到msb是0的byte停止,解析过程如下

1
2
3
4
5
96 01 = 1001 0110  0000 0001
       → 000 0001  ++  001 0110 (丢弃msb,保留剩下的 7 bits, 并且翻转顺序)
       → 10010110
       → 128 + 16 + 4 + 2 = 150

负数

在对负数进行编码的时候,使用signed int类型(sint32 and sint64)和标准类型(int32 and int64)有一些不同,使用int32或者int64时,pb会固定使用10byte来表示负数,而使用signed int类型,会先进行ZigZag编码,再用varint方式序列化。ZigZag编码方式如下:

Signed OriginalEncoded As
00
-11
12
-23
21474836474294967294
-21474836484294967295

ZigZag计算函数如下:

1
2
3
(n << 1) ^ (n >> 31), // n 为 sint32 时

(n << 1) ^ (n >> 63), // n 为 sint64 时

需要注意的是,这里的右移操作 (n>>31)是算数移动(arithmetic shift),如果n是正数,高位补0,如果n是负数,高位补1

Non-varint Numbers

Non-varint Numbers(fixed64, sfixed64, double, fixed32, sfixed32, float)就比较简单,只要根据wire_type类型读取固定长度的字节数就可以了。

Strings

wire type是2 (length-delimited)表示Value部分由Length和Data两部分组成,Length是采用varint编码的数字,表示Data的长度,然后就可以读取该长度的数据。实例如下:

1
2
3
message Test2 {
  optional string b = 2;
}

设置 b = "testing",编码如下:

1
12 07 [74 65 73 74 69 6e 67]

[ ]中的数据是UTF8 编码的 "testing"。 key是0x12,解析如下:

1
2
3
4
0x12
→ 0001 0010  (binary representation)
→ 00010 010  (regroup bits)
→ field_number = 2, wire_type = 2

Length是07表示后续7个bytes就是数据部分,直接读取7个字节即可。

Embedded Messages

Embedded Messages的wire type也是2,因此编码方式和string类似,看下例子:

1
2
3
message Test3 {
  optional Test1 c = 3;
}

设置Test1.a = 150 ,编码如下:

1
 1a 03 08 96 01

08 96 01和前面例子是一样的,根据Length 03读取三个字节就得到08 96 01,对这部分数据再进行解析即可。

Repeated字段

对于repeated字段,解析时会有多个key相同的数据,这些数据不需要连续出现。

Packed Repeated Fields

packed repeated字段会将所有数据打包到一个key-value pair 中,所有数据是连续出现,打包在一起的。

1
2
3
4
message Test4 {
  repeated int32 d = 4 [packed=true];
}

设置d = 3, 270, 86942,编码后:

1
2
3
4
5
22        // key (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)

只有原始数字类型(使用varint,32位或64位)的重复字段才可以声明为“packed”。

总结

protobuf在序列化方面,相比xml, json等具有更简单,更快,更小的优点,因此可以运用在数据传输(rpc),存储(缓存)等方面,不仅可以提高性能,还能节省大量存储空间,减少成本。

参考

Protobuf 官方文档

高效的数据压缩编码方式 Protobuf

two’s complement

Endianness

This post is licensed under CC BY 4.0 by the author.
Trending Tags
Contents

JDK源码学习之LinkedHashMap

-

Trending Tags