Redis 位数组实现

2021-11-17  本文已影响0人  wayyyy

Redis 提供了 SETBITGETBITBITCOUNTBITOP 4个命令用于处理二进制位数组。

位数组相关源码在 bitops.c 文件中。

位数组表示

Redis 使用字符串对象来表示位数组,因为字符串对象使用的SDS是二进制安全的。

image.png
SETBIT命令实现

调用栈:

(gdb) bt
#0  setbitCommand (c=0x5555558dddc8) at bitops.c:213
#1  0x0000555555577528 in call (c=0x5555558dddc8, flags=7) at redis.c:2441
#2  0x0000555555578044 in processCommand (c=0x5555558dddc8) at redis.c:2766
#3  0x00005555555865c4 in processInputBuffer (c=0x5555558dddc8) at networking.c:1539
#4  0x00005555555868b2 in readQueryFromClient (el=0x555555898158, fd=6, privdata=0x5555558dddc8, mask=1)
    at networking.c:1631
#5  0x00005555555701bf in aeProcessEvents (eventLoop=0x555555898158, flags=3) at ae.c:576
#6  0x0000555555570380 in aeMain (eventLoop=0x555555898158) at ae.c:635
#7  0x000055555557b30a in main (argc=1, argv=0x7fffffffe4d8) at redis.c:4079

setbitCommand 实现:

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);

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

    // 更新字节中的位,设置它的值为 on 参数的值
    byteval &= ~(1 << bit);
    byteval |= ((on & 0x1) << bit);
    ((uint8_t*)o->ptr)[byte] = byteval;
    
    ...
}
GETBIT 命令实现
void getbitCommand(redisClient *c) 
{
    robj *o;
    char llbuf[32];
    size_t bitoffset;
    size_t byte, bit;
    size_t bitval = 0;

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

    // 查找对象,并进行类型检查
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,REDIS_STRING)) return;

    // 计算出 offset 所指定的位所在的字节
    byte = bitoffset >> 3;
    // 计算出位所在的位置
    bit = 7 - (bitoffset & 0x7);

    // 取出位
    if (sdsEncodedObject(o)) {
        // 字符串编码,直接取值
        if (byte < sdslen(o->ptr))
            bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
    } else {
        // 整数编码,先转换成字符串,再取值
        if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
            bitval = llbuf[byte] & (1 << bit);
    }

    // 返回位
    addReply(c, bitval ? shared.cone : shared.czero);
}
BITCOUNT命令实现

BITCOUNT 命令用于统计给定位数组中,值为1的二进制位的数量,这个如果要高效的实现这个命令,需要一些精巧的算法。

BITOP命令实现

BITOP 所有操作都是用C语言内置的位操作符来实现。

上一篇下一篇

猜你喜欢

热点阅读