redis zskiplist 跳跃表源码学习

数据结构

typedef struct zskiplistNode {
    sds ele;
    double score;
    
    // 指向最下层的上一个节点
    struct zskiplistNode * backward
    
    // 层,代表跳跃表的一列,包含了每一层的这个节点
    struct zskiplistLevel {
        // 指向同一层的下一个节点
        struct zskiplistNode * forward
        
        unsigned long span;
    } level[]; // index越大表示层级越高
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

typedef struct zset {
    // 保存一个zset下 value 和 score
    dict *dict;
    
    // 指向跳跃表,对score进行排序
    zskiplist *zsl;
} zset;

添加元素

命令: ZADD key score value

zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;
    
    // 越界检查
    serverAssert(!isnan(score));
    x = zsl->header;
    
    //  从高层往下遍历
    for (i = zsl->level - 1; i >= 0; i--) {
        rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1];
        
        // 要插入的节点大于下一个节点的值,则往后走
        // 值相同时根据字符长度比较
        while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                sdscmp(x->level[i].forward->ele, ele) < 0))) {
            // 保留这个节点每一层的span
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        
        // 如果无法往后走了,走到下一层。记录这一层卡住的节点(新高度超过原有最高高度时使用)
        update[i] = x
    }
    
    // 计算节点的层级
    level = zslRandomLevel();
    // 如果出现新的高度,将原来记录的节点指向新节点
    if (level > zsl->level) {
        for (i = zsl->level; i < level; i++) {
            rank[i] = 0;
            update[i] = zsl->header;
            update[i]->level[i].span = zsl->length;
        }
        zsl->level = level;
    }
    
    // 创建一个跳跃表节点
    x = zslCreateNode(level, score, ele);
    for (i = 0; i < level; i++) {
        // 接链
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;

        /* update span covered by update[i] as x is inserted here  更新范围由update [i]覆盖,因为x插入此处 */
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels 增加跨度*/
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;
    }

    /* 更新后退指针 */
    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    zsl->length++;
    return x;
}

获取得分

命令: ZSCORE key value
从数据结构上可以看到,由于zset中存在一个dict,获取一个value的得分,应该是直接从字典拿就可以了

int zsetScore(robj *zobj, sds member, double *score) {
    if ( !zobj || !member) return C_ERR;
    if ( zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        if (zzlFind(zobj->ptr, member, score) == NULL) return C_ERR;
    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        zset *zs = zobj->ptr;
        // 从zset的字典成员中根据member直接获取entry
        dictEntry *de = dictFind(zs->dict, member)
        if (de == NULL) return C_ERR
        // 从entry中获取score
        *score = *(double *) dictGetVal(de);
    } else {
        serverPanic("Unknow sorted set encoding");
    }
    return C_OK
}

获取排名

从上面添加元素的过程中可以看到,一个节点的每一层,都会记录一个span,这个span其实就代表了它在这一层和上个节点的间隔。rank的计算就是通过查找元素的过程中累加这个间隔来得到的

unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *x;
    unsigned long rank = 0;
    int i;
    
    x = zsl->header;
    
    // 从最顶层开始查找,这里和插入的过程比较类似
    for (i = zsl->level - 1; i >= 0; i--) {
        while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                sdscmp(x->level[i].forward->ele, ele) <= 0))) {
            // 往前走一个节点,就加一次间隔
            rank += x->level[i].span;
            x = x->level[i].forward;
        }
        
        if (x->ele && sdscmp(x->ele, ele) == 0) {
            return rank;
        }
    }
    return 0;
}
参考资料

《redis深度历险》
https://www.jianshu.com/p/c2841d65df4c
https://blog.csdn.net/idwtwt/article/details/80233859

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值