Protobuf 编码(1)
本文档描述了协议缓冲消息的二进制格式。在应用程序中使用Protocol Buffer不需要理解这一点,但是了解不同的Protocol Buffer格式如何影响编码消息的大小会非常有用。
一条简单的信息
假设您有以下非常简单的消息定义:
message Test1 { optional int32 a = 1; }
在应用程序中,您创建一个Test1消息,并将设置为150。然后将消息序列化为输出流。如果您能够检查编码的消息,您会看到三个字节:
08 96 01
到目前为止,数字如此之小——但这意味着什么呢?继续阅读...
Base 128 Varints
要理解您的简单Protocol Buffer编码,您首先需要理解varints。Varints是一种使用一个或多个字节序列化整数的方法。较小的数字占用较小的字节数。
变量中的每个字节,除了最后一个字节,都设置了最高有效位( msb ),这表明还会有更多的字节。每个字节的低7位用于存储7位组中数字的二进制补码表示,最低有效组优先。
例如,这里是数字1——它是一个字节,所以msb没有设置:
0000 0001
这里是300,这有点复杂:
1010 1100 0000 0010
你怎么知道这是300?首先从每个字节中删除msb,因为这只是为了告诉我们是否已经到达数字的末尾(如您所见,它设置在第一个字节中,因为varint中有一个以上的字节) :
1010 1100 0000 0010 → 010 1100 000 0010
颠倒两组7位,因为正如您所记得的,变量首先存储具有最低有效组的数字。然后将它们连接起来,得到最终值:
000 0010 010 1100 → 000 0010 ++ 010 1100 → 100101100 → 256 + 32 + 8 + 4 = 300
消息结构
如您所知,协议缓冲消息是一系列键值对。消息的二进制版本仅使用字段的编号作为密钥—每个字段的名称和声明类型只能在解码端通过引用消息类型的定义(即.proto文件)来确定。
当消息被编码时,密钥和值被连接成字节流。当消息被解码时,解析器需要能够跳过它不能识别的字段。这样,新字段可以添加到邮件中,而不会破坏不知道它们的旧程序。为此,线格式消息中每一对的“键”实际上是两个值—来自.proto文件的字段号,加上一个线类型,该线类型提供刚好足够的信息来找到以下值的长度。在大多数语言实现中,这个键被称为标签。
可用的消息类型如下:
类型 | 意义 | 用于 |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups (已废弃) |
4 | End group | groups (已废弃) |
5 | 32-bit | fixed32, sfixed32, float |
流式消息中的每个密钥都与值(字段编号< < 3) | wire_type )不同,换句话说,数字的最后三位存储了wire类型。
现在让我们再看一遍我们的简单例子。您现在知道了,数据流中的第一个数字总是一个不同的varint 键,这里是08,或者(去掉msb ) :
000 1000
您取最后三位得到wire类型(0),然后右移三位得到字段号(1)。现在你知道字段号是1,下面的值是一个varint。使用上一节中不同的解码知识,您可以看到接下来的两个字节存储了值150。
96 01 = 1001 0110 0000 0001 → 000 0001 ++ 001 0110 (丢弃msb并反转7位组) → 10010110 → 128 + 16 + 4 + 2 = 150
未完待续...