zoukankan      html  css  js  c++  java
  • LevelDB 源码解析之 Varint 编码

    GitHub: https://github.com/storagezhang

    Emai: debugzhang@163.com

    华为云社区: https://bbs.huaweicloud.com/blogs/253047

    LevelDB: https://github.com/google/leveldb

    Varint 编码

    LevelDB 内部采用变长编码,对数据进行压缩,减少存储空间,再采用 CRC 校验数据。

    整型数据是以 32(64) 位来表示的,以 32 位为例,存储需要 4 个字节。

    如果一个整数的大小在 256 以内,那么只需要一个字节就可以存储这个整数,可以节省 3 个字节。

    Varint 就是根据这种思想来序列化整数的,它是一种使用一个或多个字节序列化整数的方法,会把整型数据编码为变长字节。

    Varint 中的每个字节都设置为最高有效位:

    • 如果该位为 0,表示结束,当前字节的剩余 7 位就是该数据的表示。

      • 表示整数 1,需要一个字节:0000 0001
    • 如果该位为 1,表示后续的字节也是该整型数据的一部分;

      • 表示整数 300,需要两个字节:1010 1100 0000 0010

    这也表示 Varint 编码后是按小端排序的。

    字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指电脑内存中或在数字通信链路中,组成多字节的字的字节的排列顺序。

    字节的排列方式有两个通用规则。例如,将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。

    因此,32 位整型数据经过 Varint 编码后占用 1~5 个字节(5 * 8 - 5 > 32),64 位整型数据编码后占用 1~10 个字节(10 * 8 - 10 > 64)。

    在实际场景中,由于小数字的使用率远远高于大数字,所以在大部分场景中,通过 Varint 编码的数据都可以起到很好的压缩效果。

    编码实现

    EncodeVarint64 将 uint64_t 编码为 Varint 类型的字节流:

    char* EncodeVarint64(char* dst, uint64_t v) {
      static const int B = 128;
      uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
    
      while (v >= B) {
        // B=128=0x80, v|B 表示在最高位上加 1
        // *ptr 是 uint8_t 类型的,即每次取下 7 位数据
        *(ptr++) = v | B;
    
        // 右移 7 位, 继续处理后面的数据
        v >>= 7;
      }
    
      // 处理最后一个字节的小于 128 的数据
      *(ptr++) = static_cast<uint8_t>(v);
      return reinterpret_cast<char*>(ptr);
    }
    

    EncodeVarint32uint32_t 编码为 Varint 类型的字节流,其实现与 EncodeVarint64 类似,但是可能因为最多 5 个字节,所以是硬编码的:

    char* EncodeVarint32(char* dst, uint32_t v) {
      uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
      static const int B = 128;
    
      if (v < (1 << 7)) {
        // v < 0x80,可以用 7 位表示,占一个字节
        *(ptr++) = v;
      } else if (v < (1 << 14)) {
        // 0x80 <= v < 0x4000,可以用 14 位表示,占两个字节
        *(ptr++) = v | B;
        *(ptr++) = v >> 7;
      } else if (v < (1 << 21)) {
        // 0x4000 <= v < 0x200000,可以用 21 位表示,占三个字节
        *(ptr++) = v | B;
        *(ptr++) = (v >> 7) | B;
        *(ptr++) = v >> 14;
      } else if (v < (1 << 28)) {
        // 0x200000 <= v < 0x10000000,可以用 28 位表示,占四个字节
        *(ptr++) = v | B;
        *(ptr++) = (v >> 7) | B;
        *(ptr++) = (v >> 14) | B;
        *(ptr++) = v >> 21;
      } else {
        // 0x10000000 <= v < 0x100000000,可以用 35 位表示,占五个字节
        *(ptr++) = v | B;
        *(ptr++) = (v >> 7) | B;
        *(ptr++) = (v >> 14) | B;
        *(ptr++) = (v >> 21) | B;
        *(ptr++) = v >> 28;
      }
      return reinterpret_cast<char*>(ptr);
    }
    

    解码实现

    解码就是编码的逆过程,同样是利用位运算进行。

    GetVarint64Ptr 将输入的 Varint 类型字节流转换成 uint64_t 整型数据:

    const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
      uint64_t result = 0;
      for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
        uint64_t byte = *(reinterpret_cast<const uint8_t*>(p));
        p++;
    
        if (byte & 128) {
          // byte & 0x80 判断最高有效位为 1
    
          // byte & 0x7f:获取 7 位有效数据
          // (b & 0x7F) << shift:Varint 编码是小端排序,每处理一个数据,都需要向高位移动 7 位
          // result | ((byte & 127) << shift):连接高位数据和低位数据
          result |= ((byte & 127) << shift);
        } else {
          // byte & 0x80 判断最高有效位为 0,最后 7 位数据
          result |= (byte << shift);
          *value = result;
          return reinterpret_cast<const char*>(p);
        }
      }
      return nullptr;
    }
    

    GetVarint32Ptr 与GetVarint64Ptr 算法相同,唯一的区别在于对小于 128 的数据进行特判,如果小于则直接返回结果,这样设计的原因是大部分数字都比 128 小,可以通过内联函数提高计算效率。

    inline const char* GetVarint32Ptr(const char* p, const char* limit,
                                      uint32_t* value) {
      if (p < limit) {
        uint32_t result = *(reinterpret_cast<const uint8_t*>(p));
        if ((result & 128) == 0) {
          *value = result;
          return p + 1;
        }
      }
      return GetVarint32PtrFallback(p, limit, value);
    }
    
    const char* GetVarint32PtrFallback(const char* p, const char* limit,
                                       uint32_t* value) {
      uint32_t result = 0;
      for (uint32_t shift = 0; shift <= 28 && p < limit; shift += 7) {
        uint32_t byte = *(reinterpret_cast<const uint8_t*>(p));
        p++;
        if (byte & 128) {
          result |= ((byte & 127) << shift);
        } else {
          result |= (byte << shift);
          *value = result;
          return reinterpret_cast<const char*>(p);
        }
      }
      return nullptr;
    }
    
  • 相关阅读:
    [转]群控电梯调度算法
    [转] 电梯调度算法总结
    [转]grub2.0和之前版本修复解决方案
    [转]Ubuntu 10.04 编译安装最新版本Linux2.6.34内核
    [转]PS2 键盘工作方式
    [转]个人管理 - 目标管理之前,你会时间管理吗
    [转]ubuntu 下编译内核简单步骤
    [转]关闭Google安全搜索,实现无限制搜索
    [转]Vim 复制粘贴探秘
    [转]Linux文件搜索
  • 原文地址:https://www.cnblogs.com/debugzhang/p/14600575.html
Copyright © 2011-2022 走看看