ProtoBuf 序列化原理
先明白大端模式和小端模式的区别
小端模式 int num = 3 在内存中的存储是,
00000011 00000000 00000000 00000000
内存地址:低-------------->高
即低位字节在前,高位字节在后
大端模式 int num = 3 在内存中的存储是,
00000000 00000000 00000000 00000011
内存地址:低-------------->高
既高位字节在前,低位字节在后,和字符串存储模式一致,更符合我们直观理解
protobuf采用的是小端模式
定义协议如下
message RequestTest{ int32 num1 = 1; string str1 = 2; uint32 num2 = 3; }
RequestTest requestTest = new RequestTest
{
Num1 = 1600,
Str1 = "abc",
Num2 = 17,
};
序列化后data在内存中的形式
00001000 11000000 00001100 00010010 00000011 01100001 01100010 01100011 00011000 00010001 tag=8 11001000000=1600 tag=18 length=3 va=97(a) va=98(b) va=99(c) tag=24 va=17 va=value
数据存储方式 T - L - V,即Tag - Length - Value 【标识 - 长度 - 字段值】。
Tag类型
2、T - L - V的具体表示
1)、Tag的具体表示
Tag = 字段序号[field_number] + 字段数据类型[wire_type] 。使用Varint编码(这个下面会讲到),占用1~5个字节的长度。
字段数据类型现在只有0~5(6种类型)其中3、4是废弃的,所以它可以用3个bit位表示,其他bit表示字段序号,这里举两个例子简单说明一下(简单交代一下每个字节的第一个bit是标记位,表示是否结束,0表示结束【这其实就是Varint的性质】)。
例一 :Tag = 【00110010】 第一个字节的第一个bit=0表示后面没有Tag的数据,OK,我们来处理这个数据。首先去掉标记位得到新的数据【0110010】。最后3个bit = 010 = 2,表示字段数据类型为2(6种类型中的一种);剩下的4个bit = 0110 = 6,表示字段序号是6。
例二:Tag = 【10000010 00000001】 第一个字节的第一个bit=1表示后面还有Tag的数据,第二个字节的第一个bit=0表示结束,OK,我们来处理这个数据。首先将两个字节颠倒位置(Protobuf是小端编码),得到新的数据【00000001 10000010】,然后将标记位去掉又得到一个新的数据 【 0000001 0000010】。最后3个bit = 010 = 2,表示字段数据类型为2(6种类型中的一种);剩下的11个bit = 00000010000 = 16,表示字段序号是16。
2)、Length的具体表示
使用Varint编码(这个下面会讲到),占用1~5个字节的长度。数值表示后面数据(Value)的长度,现在只有字段数据类型 = 2 时需要Length字段。
3)、Value的具体表示
表示具体的数据,采用小端编码。String类型采用UTF-8编码。
3、编码方式
1)、Varint编码方式
a、简介
一种变长的编码方式,优点是对于小的数值可以用很少的字节表示,从而进行数据压缩。
存储方式是 T - V。
b、原理
Varint编码的每个字节的最高位都有特殊含义:
如果是1,表示后续的字节也是该数值的一部分;如果是0,表示这是最后一个字节。
因此:小于128的数值可以用1个字节就表示了。上面在解释Tag的具体表示时已经举例过两个字节的计算方式,在这里再举一个3字节的例子加强一下认识。
例子:【10101100 10000011 00000001】 (注意这里假定这个数据不是 sint32 / sint64 类型 ,后面讲到Zigzag编码方式时会说明)。
可以看出第一个字节的最高位=1,说明后面的字节也是该数值的一部分,看第二个字节的最高位也是=1,同理,我们又看第三个字节的最高位=0,说明这是最后一个字节。OK,我们确定了数值的数据是3个字节表示的,现在开始计算。首先颠倒着3个字节(Protobuf是小端编码 后面遇到不会再做强调),得到新的数据【00000001 10000011 10101100】,然后将标记位去掉又得到一个新的数据【0000001 0000011 0101100】,计算得出数值为16812 。
讲到这里就要说一下Varint编码方式的不足了,在计算机里,负数的最高位是1(符号位),所以在用Varint编码时会被当成很大的整数,这就不利于数据的压缩了。解决方案是Protobuf定义了sint32 / sint64 的类型表示负数,通过先采用Zigzag编码(将有符号数 转换成 无符号数),再采用Varint编码,从而减少编码后的字节数。