illx10000

青春不是年华,而是心境

protobuf序列化学习

2019年2月15日 星期五, 发表于 深圳

如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)

protobuf 整数编码 中使用了varint和zigzag对整数进行编码,达到节省空间的目的;

如文章有任何冒犯之处,例如侵权或者未标明引用,请联系删除。
本人水平有限,如有错误之处,请不吝赐教。

1. varint编码

看到varint编码了,首先想到了 huffman编码 ,huffman编码通过统计字符出现频率获取变长编码表,使用变长编码表对源符号进行编码,这样使 得编码后的字符长度降低,达到数据压缩的效果;

varint和huffman都是变长编码,varint可以压缩较小的数(protobuf认为在实际应用中较小的数具有较高的出现频次),varint用较少的byte表示较小的数,例如int32类型的数值,一般需要4个byte来表示,在varint编码中可以使用1byte表示小数,但是遇到大数可能需要5byte;

看看varint具体的编码方式;https://www.cnblogs.com/smark/archive/2012/05/03/2480034.html

varint编码每一个Byte的最高位bit有特殊含义;如果为1,则表示下一个Byte也是该数值的一个部分,如果该位置为0,则结束,使用剩下的7bit来表示数值,因此,小于128的数值都可以用一个byte表示;

//进行varint32编码
int encodeVarint32( unsigned char* inBuff, uint32_t num)
{
    const int B = 128;
    int iEncodeLen = 0;
    while (num >= B)
    {
        *(inBuff + (iEncodeLen++)) = (unsigned char)((num & (B -1)) | B);
        num >>= 7;
    }

    *(inBuff + (iEncodeLen++)) = num;

    return iEncodeLen;
}

//进行varint32解码
uint32_t decodeVarint32( unsigned char* inBuff)
{
    uint32_t b = 0,len = 0;
    while (true)
    {
        b |= ((inBuff[len] & (0x7f) ) << (7*len));
        if ( (inBuff[len] & 0x80) != 0x80) break;
        len++;
    }
    return b;
}

c++ 的位操作优先级比较低,编码的时候需要特别注意,最好多使用括号,例如在上面的语句

if ( (inBuff[len] & 0x80) != 0x80) break; //正确

if ( inBuff[len] & 0x80 != 0x80) break; //错误

2. ZigZag编码

ZigZag编码是将有符号数统一映射到无符号数的一种编码方式;

这篇文章写的很好:https://blog.csdn.net/zgwangbo/article/details/51590186

1. 补码的第一位是符号位,他阻碍了我们对于前导0的压缩,那么,我们就把这个符号位放到补码的最后,其他位整体前移一位;
2. 因为数字绝对值越小,他所含的前导1越多。于是,这个算法就把负数的所有数据位按位求反,符号位保持不变,得到了这样的整数;
uint32_t encodeZigzag(int32_t num)
{
    return (( num << 1) ^ ( num >> 31));
}

int decodeZigzg(int num) 
{
    return (((uint32_t)num) >>1) ^ -(num & 1);
}

使用ZigZag编码结合varint32编码就可以对绝对值较小的数进行编码,节省部分空间;