Redis 源码解析(6)BitMap

简介

Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。

由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。

![在这里插入图片描述](https://img-blog.csdnimg.cn/a12a90b5153a421d9867694a3b9ead1e.png

内部实现

Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。

String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态,你可以把 Bitmap 看作是一个 bit 数组。

// 查找字符串对象
    o = lookupKeyWrite(c->db,c->argv[1]);
    if (o == NULL) {

        // 对象不存在,创建一个空字符串对象
        o = createObject(REDIS_STRING,sdsempty());

        // 并添加到数据库
        dbAdd(c->db,c->argv[1],o);

    } else {

        // 对象存在,检查类型是否字符串
        if (checkType(c,o,REDIS_STRING)) return;

        o = dbUnshareStringValue(c->db,c->argv[1],o);
    }

bitmap修改

/* SETBIT key offset bitvalue */
void setbitCommand(redisClient *c) {
    robj *o;
    char *err = "bit is not an integer or out of range";
    size_t bitoffset;
    int byte, bit;
    int byteval, bitval;
    long on;

    // 获取 offset 参数
    if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
        return;

    // 获取 value 参数
    if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
        return;

    /* Bits can only be set or cleared... */
    // value 参数的值只能是 0 或者 1 ,否则返回错误
    if (on & ~1) {
        addReplyError(c,err);
        return;
    }

    // 查找字符串对象
    o = lookupKeyWrite(c->db,c->argv[1]);
    if (o == NULL) {

        // 对象不存在,创建一个空字符串对象
        o = createObject(REDIS_STRING,sdsempty());

        // 并添加到数据库
        dbAdd(c->db,c->argv[1],o);

    } else {

        // 对象存在,检查类型是否字符串
        if (checkType(c,o,REDIS_STRING)) return;

        o = dbUnshareStringValue(c->db,c->argv[1],o);
    }

    /* Grow sds value to the right length if necessary */
    // 计算容纳 offset 参数所指定的偏移量所需的字节数
    // 如果 o 对象的字节不够长的话,就扩展它
    // 长度的计算公式是 bitoffset >> 3 + 1
    // 比如 30 >> 3 + 1 = 4 ,也即是为了设置 offset 30 ,
    // 我们需要创建一个 4 字节(32 位长的 SDS)
    byte = bitoffset >> 3;
    o->ptr = sdsgrowzero(o->ptr,byte+1);

    /* Get current values */
    // 将指针定位到要设置的位所在的字节上
    byteval = ((uint8_t*)o->ptr)[byte];
    // 定位到要设置的位上面
    bit = 7 - (bitoffset & 0x7);
    // 记录位现在的值
    bitval = byteval & (1 << bit);

    /* Update byte with new bit value and return original value */
    // 更新字节中的位,设置它的值为 on 参数的值
    byteval &= ~(1 << bit);
    byteval |= ((on & 0x1) << bit);
    ((uint8_t*)o->ptr)[byte] = byteval;

    // 发送数据库修改通知
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
    server.dirty++;

    // 向客户端返回位原来的值
    addReply(c, bitval ? shared.cone : shared.czero);
}

从源码可以看到

  • 先使用byte = bitoffset >> 3来获取偏移量对应的字节数组中的位置。
  • 再使用bit = 7 - (bitoffset & 0x7);来得到字节中的比特位
  • 最后使用((uint8_t*)o->ptr)[byte] = byteval;对比特位进行赋值

注意:bitmap实际上就是一个sds,而sds最大的长度为512mb,所以一个bitmap最大可以有51210241024*8=4,294,967,296 大约43亿位的数据。

/*
 * 检查给定字符串长度 len 是否超过限制值 512 MB
 *
 * 超过返回 REDIS_ERR ,未超过返回 REDIS_OK
 *
 * T = O(1)
 */
static int checkStringLength(redisClient *c, long long size) {

    if (size > 512*1024*1024) {
        addReplyError(c,"string exceeds maximum allowed size (512MB)");
        return REDIS_ERR;
    }

    return REDIS_OK;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值