【leveldb】varint 讲解和实现

leveldb 同时被 3 个专栏收录
3 篇文章 0 订阅
1 篇文章 0 订阅
15 篇文章 0 订阅

前言

  最近在学习leveldb,遇到varint这种数据结构,本来想直接看网上的解析,然而网上的解析都千篇一律,都是贴代码让自己去看。好不容易弄懂之后决定把思考过程贴出来,方便大家学习。
  leveldb中为了减少内存占用,使用了varint这一数据结构,把数字放到字符数组中来表示,思想就是把小数字用尽量少的字节来表示,每个字节只使用其中的7位,最高位用来表示是否还有剩余的数字,0代表没有,1代表有。

encode

代码

下面是encode的代码

#include <stdio.h>
#include <inttypes.h>
#include <string>

char *encode_varint32(char *dst, uint32_t value) {
    uint8_t *ptr = reinterpret_cast<uint8_t*>(dst);
    // B = 0b 1000 0000
    const int B = 128;
    if(value < (1 << 7)) {
        *(ptr++) = value;
    } else if (value < (1 << 14)) {
        *(ptr++) = value | B;
        *(ptr++) = value >> 7;
    } else if (value < (1 << 21)) {
        *(ptr++) = value | B;
        *(ptr++) = value >> 7;
        *(ptr++) = value >> 14;
    } else if (value < (1 << 28)) {
        *(ptr++) = value | B;
        *(ptr++) = value >> 7;
        *(ptr++) = value >> 14;
        *(ptr++) = value >> 21;
    } else {
        *(ptr++) = value | B;
        *(ptr++) = value >> 7;
        *(ptr++) = value >> 14;
        *(ptr++) = value >> 21;
        *(ptr++) = value >> 28;
    }
    return reinterpret_cast<char*>(ptr);
}

讲解

  首先把字符数组转为uint8_t类型的数组,这样我们接下来才能表示数字。可以看到,在判断语句中对数字所需要的位数进行了判断,如果可以用7个位来表示,那就直接赋值,如果可以用14个位来表示(剩下两位要用来表示数字是否结束),那就把数字的前7位放到dst[0]中,在把剩余的7位右移,放到dst[1]中去。如果可以用21个位来表示,那么前七个位放到dst[0],中间七位放到dst[1],最后七位放到dst[2]
  举例说来,假设我们的dst是一个长度位5的字符数组,value等于129。那么dst在内存空间中是这样表示的dst[4] dst[3] dst[2] dst[1] dst[0],每个占据8位,value的二进制表示为0000 0000 1000 0001ptr首先指向dst[0],现在我们通过*(ptr++) = 128 | value把最后七位0000 0001放到dst[0]中去,此处发生了截断,因为*ptruint8_t类型,而valueuint32_t,所以只会把value的最后七位放到dst[0]中去,放完之后dst[0]等于1000 0001,注意此处最高位的1并不是数字,而是代表数字没有结束,要继续操作。接下来我们把中间七位00 0000 1放到dst[1]中去,首先右移七位得到0000 0001,然后直接赋值即可。

decode

代码

void decode_varint32(const char *src, /*const char *limit,*/ uint32_t *value) {
    const char *p = src;
    uint32_t v = *reinterpret_cast<const uint8_t*>(p);
    const int B = 128;
    *value = 0;

    uint8_t shift;
    for(shift = 0; shift <= 28 && (v&B)/* p < limit*/ ; shift += 7) {
        //0x7f = 0b 0111 1111
        *value |= ((v & 0x7f) << shift);
        p++;
        v = *reinterpret_cast<const uint8_t*>(p);
    }
    if(shift <= 28) {
        *value |= ((v & 0x7f) << shift);
    }
}

讲解

  decode就是encode的逆过程,每次从数组中拿出七位,移位到最终的位置后与结果按位或,直到字节的最高位为0代表结束即可。
  举例说来,刚才我们把129放到了dst中,现在dst的内容是:dst[0]存放1000 0001dst[1]存放0000 0001。ok,我们现在从dst[0]开始取,因为要消去最高位的标识位,所以要和0111 1111 按位与即& 0x7fdst[0]代表最后七位,移位0位即可,现在我们的*value等于1。接下来对dst[1]进行循环条件判断,一位内dst[1]=0000 0001不符合条件,进入最后的if语句,在这里我们接着取dst[1]的内容,因为dst[1]中存放中间七位,我们取出数据后消去标志位,然后左移七位得到1000 0000,然后与刚才的结果按位或得到最终结果129。
  这里我对leveldb中的代码(源码见GetVarint32PtrFallback)做了些改动,看起来更简洁一些,另外我觉得leveldb中的const char *dst是不必要的,因为已经有了最高位来判断数字是否结束,当然,我们写代码的时候最好还是加上,更严格的控制条件并没什么不好,相反可能还会有好处,这里只是提供一个另外的思路。

测试

int main() {
    char buf[5] = {0};
    char *ptr = encode_varint32(buf, 279);
    uint32_t b = 0;
    decode_varint32(buf, /*ptr,*/ &b);
    printf("%" PRIu32"", b);
    
    encode_varint32(buf, 876);
    decode_varint32(buf, &b);
    printf("%" PRIu32"\n", b);

    encode_varint32(buf, 65532);
    decode_varint32(buf, &b);
    printf("%" PRIu32"\n", b);


    encode_varint32(buf, 23456);
    decode_varint32(buf, &b);
    printf("%" PRIu32"\n", b);

}

在这里插入图片描述

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值