protobuf序列化算法原理

之前那篇文章,讲过Json里的序列化结果为: { "name":"chenpp","age":21} -- 一共26个字节,而想要将其进行进一步压缩,就需要去掉一些冗余的字节

思路:1)能不能去掉定义属性(约定1=name,2=age) 约定了字段,约定了类型 去除分隔符(引号,冒号,逗号之类的)

2)压缩数字,因为日常经常使用到的都是一些比较小的数字,一共int占4个字节,但实际有效的字节数没有那么多

把英文转化成数字(ASCII),并对数字进行压缩

protobuf里使用到了两种压缩算法:varint和zigzag算法

varint算法
这是针对无符号整数,一种压缩方式

压缩方法:

1.对一个无符号整数,将其换算成二进制

2.从右往左取7位,然后在最高位补1

3.一直继续,直到取到最后一个有意义的字节(在最后一个有意义的字节上最高位补0)

4.先取到的字节排在后取到的字节前面,得到一个新的字节,转换成十进制就是压缩的结果

以:500为例:其实有意义的就是2个字节

0000 0001 1111 0100= 2^2+2^4+2^5+2^6+2^7+2^8 = 4+16+32+64+128+256 = 500

按照其压缩方式得到的新的二进制字节为:

1111 0100 | 0000 0011

1111 0100 代表的是负数,使用补码(正数取反+1),并且最高位符号位为1

转化后: 先 -1为 1111 0011 取反 0000 1100 = 12

计算出来就是 -12 3

字符如何转化成数字编码
对于英文字母,这里的name字段,使用ASCII码对照表查找对应的数字

chenpp: 对应的ASCII码

c-99 h-104 e-101 n-110 p-112

按照varint的算法 取7位补最高位为1(最后一个字节最高位补0)

对于小于127(2^7-1=127)的数字,其有效字节只有1位,压缩的时候最高位补0,故压缩之前和压缩之后的数字没有变化

protobuf的存储格式
protobuf采用T-L-V的格式进行存储

[Tag | length | value ]

l其中length为可选, 但是string必须有length(这样在反序列化的时候程序才知道该字符串从哪里开始到哪里结束), 而int是不需要length的

Tag:字段标识符,用于标识字段 其值等于

field_number(当前字段的编号,第几个字段)<<3|wire_type(int64/int32/可变长度string)

Length:Value的字节长度 (string需要有,int不需要)

Value:消息字段经过编码后的值

Age:int32 2<<3|0 = 16

Name: string 1<<3|2 = 10

在反序列化的时候根据tag mod 8 的余数判断对应的wireType,从而知道该字段对应的存储方式和编码方式

对应User(name="chenpp",age=21)按照varint进行压缩后其序列化结果为:

c-99 h-104 e-101 n-110 p-112

String:tag-length-value

Int32:tag-value

10 6 99 104 101 110 112 112 16 21

Tag – length - c - h – e - n - p - p - Tag - Value

根据上述压缩算法可知:对于int32/int64,value有且只有最后一个字节为正数,故当遇到第一个为正数的字节时就知道其value值已经获取完毕,所以对于int32类型的字段,不需要length,只需要tag和value就足够了

ZigZag算法
在计算机中,负数会被表示为很大的整数,因为负数的符号位在最高位,如果使用varint算法进行压缩的话会需要 32/7 ~ 5个字节,反

而加大了空间的开销.故在protobuf中对于有符号整数会使用sint32/sint64来表示。protobuf中负数的压缩方式是先使用ZigZag算把有符号数(无论数值是正数还是负数,都会进行一次压缩计算)转化为无符号数,再使用varint进行压缩

ZigZag算法的思路:

负数之所以不好压缩:一个原因是因为其最高位为1,另一个原因是对于绝对值比较小的负数,其正数会有很多的前导零,那么在使用补码表示负数的时候(取反+1),会导致负数会有很多的前导1,使得无法压缩

所以ZigZag采用的办法就是:先将符号位从最高位移动到最低位,其余数字均往前移动1位;然后再对所有的数字(符号位除外)进行取反,这样得到的计算结果就是一个可以压缩的数字(符号位不占据最高位,而小绝对值的数值由于取反操作其前导1都变为了前导0)

比方说-300:

其对应的正数的原码为: 0000 0000 0000 0000 0000 0001 0010 1100

取反: 1111 1111 1111 1111 1111 1110 1101 0011

再+1: 1111 1111 1111 1111 1111 1110 1101 0100 (-300)

移动符号位之后: 1111 1111 1111 1111 1111 1101 1010 1001

取反:0000 0000 0000 0000 0000 0010 0101 0111

计算后为0010 0101 0111 = 599

在ZigZag算法里也是使用这种思路对有符号整数进行压缩的,将其转化成表达式就是

Sint32: (n<<1)^ (n>>31)

Sint64: (n<<1)^(n>>63)

当n为正数时,n>>31为0000 0000 0000 0000 0000 0000 0000 0000;当n为负数时,n>>31为1111 1111 1111 1111 1111 1111 1111 1111

(n>>31)与(n<<1)进行异或之后,如果n为正数,(n>>31)^(n<<1) =n<<1;如果n为负数,其计算结果和上述所说的最高位移动到最后,然后取反效果是一样的(n<<1补的最低位为0和n>>31异或运算之后一定为1,而其他位上与1做异或运算相当于取反),这样一来就可以使用varint进行压缩计算了

对-300的ZigZag计算结果:599 使用varint算法进行压缩

得到1101 0111 0000 0100

其结果为:-41 4

到此,protobuf的压缩原理就介绍完了
https://blog.csdn.net/qq_35448165/article/details/99473027

原文地址:https://www.cnblogs.com/ckAng/p/15795638.html