protobuf--Varint编码

Varint编码:
一种变长的编码方式。用字节表示数字值越小的数字,使用越少的字节数表示。通过减少表示数字的字节数从而进度数据的压缩。
变长:采用一个或者多个字节表示一个数字,对于小的数字使用一个字节,大的数需要5个字节。
实现方式:每个字节的最高位为1,表示后续的一个字节也是数字的一部分。如果字节的最高位为0,则表示结束。使用其它7位来表示数字。
所以小于128的数字,使用一个字节就可以表示,大于128字节的需要多个字节。

注:先不考虑负数


Varint编码

 

说明:
    1、取低7位,高位补零。
    2、右移7位后,如果右移后不为0,再取7位直接拼接;如果为0则终止。 
    所以Varint 为LE 字节序

Varint解码:

 

说明:
    1、取第一个字节,取低7位。如果第一个字节小于0(高位为1,值为负数)进行下一步
    2、取下一个字节移左移7位,拼接。不足字节数高位补0

java代码:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.CorruptedFrameException;

/**
 * netty ProtobufVarint32LengthFieldPrepender()、解码器ProtobufVarint32FrameDecoder()
 */

public class Test {

    public static void main(String[] args) throws Exception {

        int n = 300;

        //编码,编码后的为LE 字节序
        byte[] varint32 = varint32Encode(n);

        //解码
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        buffer.writeBytes(varint32);
        int decode = readRawVarint32(buffer);
        System.out.println("decode:"+decode);
    }

    /**
     * 解码,最多只有5个字节, 这个代码是ProtobufVarint32FrameDecoder.readRawVarint32 方法的源码
     * @param buffer
     * @return
     */
    private static int readRawVarint32(ByteBuf buffer) {
        //不可读
        if (!buffer.isReadable()) {
            return 0;
        }
        buffer.markReaderIndex();
        //读取一个字节
        byte tmp = buffer.readByte();
        if (tmp >= 0) {
            //如果大于 0 ,说明只有一个字节(如果大于一个字节,高位为1,值为负)
            return tmp;
        } else {
            //取temp低7位
            int result = tmp & 127;

            if (!buffer.isReadable()) {
                //如果读取一个字节后,不可再读(读指针大于等于写指针,源码注释有点问题),说明这个值只有一个字节,最高位为1,值错误返回 0。
                buffer.resetReaderIndex();
                return 0;
            }
            if ((tmp = buffer.readByte()) >= 0) {
                //取第二个字节,如果第二个字节大于0,则左移7位(只有7位表示数字,不够一个字节则高位补0),
                result |= tmp << 7;
            } else {
                //第二个字节取低7位,左移7位,与result 取或
                result |= (tmp & 127) << 7;
                if (!buffer.isReadable()) {
                    //值错误
                    buffer.resetReaderIndex();
                    return 0;
                }
                if ((tmp = buffer.readByte()) >= 0) {
                    //取第三个字节,和上面一样
                    result |= tmp << 14;
                } else {
                    result |= (tmp & 127) << 14;
                    if (!buffer.isReadable()) {
                        buffer.resetReaderIndex();
                        return 0;
                    }
                    if ((tmp = buffer.readByte()) >= 0) {
                        //取第四个字节,和上面一样
                        result |= tmp << 21;
                    } else {
                        result |= (tmp & 127) << 21;
                        if (!buffer.isReadable()) {
                            buffer.resetReaderIndex();
                            return 0;
                        }
                        //取第五个字节
                        result |= (tmp = buffer.readByte()) << 28;
                        if (tmp < 0) {
                            throw new CorruptedFrameException("malformed varint.");
                        }
                    }
                }
            }
            return result;
        }
    }

    /**
     * 编码
     * @param n
     */
    public static byte[] varint32Encode(int n){
        //n编码后,占用size个字节
        int size = size(n);

        byte[] varint32 = new byte[size];

        int idx = 0;

        while (true) {
            if ((n & ~0x7f) == 0) {
                //小于等于127 (与上 -128 (只有低7位为0) 等于 0 )
                varint32[idx++] = (byte) n;
                break;
            } else {
                //大于127

                /**
                 * n & 0x7f  取出 n的 后 7位
                 *  | 0x80  把第8位补为 1
                 */
                varint32[idx++] = (byte) ((n & 0x7f) | 0x80);

                //无符号右移7位
                n >>>= 7;
            }
        }
        for (byte b : varint32) {
            //打印出数据,不够8位高位补0。 LE 字节序
            //大于127的数字,最高位为1,所以编码后都是负数
            System.out.print(Integer.toBinaryString(b)+" ");
        }
        System.out.println();
        return varint32;
    }

    /**
     * 计算n 需要byte 数组长度。 最多5个字节
     * @param n
     * @return
     */
    public static int size(int n){
        if ( (n & (0xffffff << 7)) ==0) return 1;
        if ( (n & (0xffffff << 14)) ==0) return 2;
        if ( (n & (0xffffff << 21)) ==0) return 3;
        if ( (n & (0xffffff << 28)) ==0) return 4;
        return 5;//如果是负数
    }
}

 

打印结果:
11111111111111111111111110101100 10 
decode:300

分析:
    300 (00000001 00101100) Varint32 编码后二进制为:11111111111111111111111110101100 10  
    java 中int 为4字节32位,值为负数,所以这位全为1,只看最后8位即可
    300 使用 varint32编码后战胜2个字节,如上面打印出的值,取两个字节所以上面的Varint32编码为:10101100 00000010
   解码使用netty 的实现。参考netty 的实现(4.1.24.Final 版本)
       ProtobufVarint32LengthFieldPrepender 编码
       ProtobufVarint32FrameDecoder.readRawVarint32 方法就是解码Varint32的


Varint32编码不足:
    如果很大的数(负数)会使用5个字节去表示

    所有对于 int32、 int64 的类型, protobuf使用Varint32 编码
    对于sint32、sint64 类型的字段值(负数),Protocol Buffer会先采用 Zigzag 编码,再采用 Varint编码

Zigzag 编码:
Zigzag 编码是补充Varint32编码在表示负数的不足,使用正数去表示一个负数。从而更好的帮助  Protocol Buffer进行数据的压缩。

代码:

    

    public static void main(String[] args) {

        int n = -2;

        //编码
        // 对于sint 64 数据类型 则为: return  (n << 1> ^ (n >> 63) ;
        int zigzagEncode = (n <<1) ^ (n >>31);

        //解码
        int zigzagDecode = (zigzagEncode >>> 1) ^ -(zigzagEncode & 1);

        System.out.println("zigzagEncode:"+zigzagEncode);
        System.out.println("zigzagDecode:"+zigzagDecode);
    }

也就是两个公式。

对于可能是负数的值,使用有符号的类型。

 

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值