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]}"); }