MSB为0代表一个数字结束,1表示还未结束;这样对于较小的数字比较节省空间,当然大的数字可能要多一个字节。
采用小端表示,以下为300的Base 128 Varints表示的解码过程。
varints 编码:10101100 00000010
去掉最高位: 0101100 0000010
小端->大端: 0000010 0101100
去掉前面的0: 10 0101100
二进制->10: 300
protobuf格式的消息只是K,V对,K为字段的id而不是名字用于节省空间。
到了服务端后通过消息定义文件(如.proto或类定义文件)可以重新从id获得名字。
序列化中,类型信息和字段ID合在了一起 (field_number << 3) | wire_type。wire types 只包含类型的大小信息,不指出具体哪种类型,最多3位。
protobuf的varints编码策略就是:假定程序使用过程中,较小的数使用较多;于是将小的数编码为更少的位数。
这个策略很直白,是从整体上应用,而不是像哈夫曼编码那样具体分析每次数据中每个字符的频率。
对于有符号整数sint32和sint64,也应用了这个策略,使用了Zigzag编码:假定程序使用过程中,绝对值越小的使用频率越高;于是将绝对值小的数编码为更少的位数。
即:-1编码为1,1编为2,-2编为3,2编为4,-3编为5......。
换句话说,每个值n都按下式编码:
(n << 1) ^ (n >> 31)
如果是64bit,那么用下面的式子:
(n << 1) ^ (n >> 63)
实际上个人使用过程中应该是在某个范围内的正数,使用频率比负数要高。但是这里是编码需要一种简单的策略,而不是压缩。
varint指的是变长的,包括int32,、sint32、int64、sint64。
non-varint就是指采用不变长的方式编码。包括32位的float、fixed32、sfix32以及64位的double、fixed64、sfixed64。其中fixed64,fixed32、sfix32、sfixed64其实和int32,、sint32、int64、sint64表达能力一样,只是采用了平常使用的定长方式编码。
wire type 2(length-delimited)意味着后面跟的value是一个用varint编码的length,然后跟着一个length指定长度的bytes。
message Test2 {
required string b = 2;
}
如果b被set为“testing”, 那么你会得到下面这个序列:
12 07 74 65 73 74 69 6e 67
标红色的bytes是UTF8编码下的“testing”。
这个stream中的key是0×12 –> id=2, type=2。(id << 3) | wire_type。也就是10 010,10进制为18,16进制为12。
表示长度的varint是7。
随后跟着7个bytes,就是我们的string。
下面定义了一个message,里面又包含了一个Test1类型的message:
message Test3 {
required Test1 c = 3;
}
下面是编码后的版本
1a 03 08 96 01
后三个byte正是第一个例子中的结果(08 96 01), 150的varint编码。
而且他们前面的byte是3(也就是length)。
id=3, wiretype = 2。 (id << 3) | wire_type。11 010,16进制为1a。
可以看出embedded message的处理方法和string是完全一样的(wiretype = 2).
没有加[packed=true]选项,每个元素都还要加 key,但是不需要排列一起可以和其他字段穿插,这个特点没什么用。
新版本可以加[packed=true]选项,只需要写一次key就行了。但是不能和其他字段穿插。
例如,这样的一个message type:
message Test4 {
repeated int32 d = 4[packed=true];
}
假设你创建了一个Test4对象,repeated field d中存入了3,270,86942. 那么,编码后的形式将会是:
22 // tag (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)
只有类型为原始数字类型的repeated field才能被声明为“packed”。