zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Spring中 @PathVariable
    消息队列中点对点与发布订阅区别
    rabbitMQ下载地址
    当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
    String和StringBuilder、StringBuffer的区别?
    char 型变量中能不能存贮一个中文汉字,为什么?
    抽象类(abstract class)和接口(interface)有什么异同?
    静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?
    抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?
    阐述静态变量和实例变量的区别。
  • 原文地址:https://www.cnblogs.com/ckAng/p/15795638.html
Copyright © 2011-2022 走看看