C# 学习varint

varint是一种可变长度数值表示法,具体的原理是每个字节只用后7位bit来表示数值,最高位作为msb标识,为1时表示后续还有字节,为0时则说明是最后一个字节,

因此,如果数值在0-128之内,就能直接用一个字节来表示,相比于固定4个字节的存储方式,能节省3个字节,简直是血赚,

但是,如果数值很大的话,那么就需要5个字节来表示,这就比较亏了,而且由于负数的最高位一直是1,这就导致了负数永远是血亏的,大佬们对于负数有者其他特殊的手段来处理,不过我这里只研究了正数的处理方式

代码是自己摸索出来的,可能有不对的地方,不建议直接拿来食用,如果有的话麻烦指出啊,相互学习进步

    /// <summary>
    /// 基于varint数字压缩的一个实现,
    /// 因为负数的最高位是1,这导致了压缩后占用的字节更多,因此该类只支持32位的无符号整形,
    /// 如果一定要用int32,可以写的时候在外部把int转成uint传入,读的时候把返回的uint转成int
    /// </summary>
    internal class Varint {
        /// <summary>
        /// 向外部缓冲区中写入一个数值的varint压缩后的字节流
        /// </summary>
        /// <param name="value">数值</param>
        /// <param name="buffer">外部缓冲区,传入后填充,避免创建额外的字节数组</param>
        /// <param name="offset">缓冲区的偏移量</param>
        /// <returns>写入缓冲区中的字节长度</returns>
        public static int Write(uint value, byte[] buffer, int offset) {
            int bytelength = 0;
            while (value > 0x7F) { //127=0x7F=0b01111111,大于说明msb=1,即后续还有字节
                uint temp = value & 0x7F; //得到数值的后7位,0x7F=0b01111111,0与任何数与都是0,1与任何数与还是任何数
                temp |= 0x80; //后7位不变最高位固定为1,0x80=0b10000000,1与任何数或都是1,0与任何数或都是任何数 
                buffer[offset++] = (byte)temp; //存储msb=1的数据
                value >>= 7; //右移已经计算过的7位得到下次需要计算的数值
                bytelength++;
            }
            buffer[offset++] = (byte)value; //最后一个字节msb=0
            return bytelength + 1;
        }
        /// <summary>
        /// 从缓冲区的指定偏移量开始读取varint字节流,返回读取到的数值,以及数值在缓冲区中占用字节长度
        /// </summary>
        /// <param name="buffer">外部缓冲区</param>
        /// <param name="offset">缓冲区的偏移量</param>
        /// <returns>第一个参数value表示解析出来的值,第二个参数bytelength表示value的在buffer中的varint字节数组的长度</returns>
        public static (uint, int) Read(byte[] buffer, int offset) {
            uint value = 0;
            int bytelength = 0;
            while (true) {
                byte b = buffer[offset++];
                int temp = (b & 0x7F); //取每个字节的后7位
                temp <<= (7 * bytelength); //向左移位,越是后面的字节,移位越多
                value += (uint)temp; //把每个字节的值加起来就是最终的值了
                bytelength++;
                if (b <= 0x7F) { //127=0x7F=0b01111111,小于等于说明msb=0,即最后一个字节
                    break;
                }
            }
            return (value, bytelength);
        }
    }

 测试代码

        static void Test() {
            byte[] buffer = new byte[1024]; //模拟外部缓冲区
            int offset = 256; //假如缓冲区中已经写了256个数据了
            int value = 300;
            offset += Varint.Write((uint)value, buffer, offset); //写完varint后,缓冲区依然要写数据,因此要累加偏移量
            buffer[offset++] = 255;  //模拟写完varint后继续写数据
            offset = 256; //重置偏移量
            (uint strLength, int byteLength) = Varint.Read(buffer, offset);
            Console.WriteLine($"value={(int)strLength},在缓冲区中占用{byteLength}个字节");
            offset += byteLength;
            Console.WriteLine($"varint之后写入的数据是{buffer[offset]}");
        }

 

posted @ 2022-10-17 18:27  WmW  阅读(112)  评论(0编辑  收藏  举报