redis源码之跳表(十二)

简介

redis的有序集合底层使用了跳表进行实现,其查找性能和红黑树相似,也是log(N),实现起来比红黑树简单。

有序集合命令

# 创建有序集合test 其中的成员为(3,dsq)(6,b)(9,c), 3,6,9是用来排序的,称为分数
ZADD test  3 dsq  6 b 9 c
# 在集合test中查询dsq的分数,返回3
zscore test dsq
# 查询dsq的排名,返回0, 排名是从0开始的
zrank test dsq
# 查询排名在0到2之间的对象,返回dsq,b,c, 这里是按照从小到大排序的;如果想从大到小,可以使用负分数
zrange test 0 2
# 删除dsq
zrem test dsq
  • 这里需要注意的是有序集合支持查找指定排名的元素。

源码分析

数据结构

typedef struct zskiplistNode {
    // 键值
    sds ele;
	   // 分数,用来排序
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
	       // 当前层的下个节点
        struct zskiplistNode *forward;
		     // 与当前层下个节点的排名距离
        unsigned long span;
    } level[];
} zskiplistNode;

插入数据

zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
	   // rank[i]记录了要插入的节点的前一个节点的排名
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));
    x = zsl->header;
    // 从最高层开始查找,一直到第0层
    for (i = zsl->level-1; i >= 0; i--) {
        /* store rank that is crossed to reach the insert position */
        // 计算当前扫描到的节点的排名, 也就是插入点前面的节点的排名
        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)))
        {
            // 水平移动的时候 rank计算方式
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        // 记录了当前层插入点前面的节点
        update[i] = x;
    }
    /* we assume the element is not already inside, since we allow duplicated
     * scores, reinserting the same element should never happen since the
     * caller of zslInsert() should test in the hash table if the element is
     * already inside or not. */
	   // 插入数据的层高是随机的
    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);
    // 在0到level-1层插入节点x
    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 */
        // 插入节点的排名应该是rank[0]+1
        // rank[i]是update[i]的排名
        // x节点与下个节点之间的距离就是下个节点的排名减去当前节点的排名
        // 下个节点的排名为 rank[i]+update[i]->level[i].span
        // x节点的排名为rank[0]
        // 这里改写为(update[i]->level[i].span+rank[i]) - rank[0]更加容易理解
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
        // 这里改写成(rank[0]+1)-rank[i] 更加易于理解
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
	   // 对于大于level的节点,因为插入了一个节点,所以span要加1
    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;
}

  • 这里最难理解的就是span了,span就是为了快速查找到排名为n的元素而设定的
posted @ 2024-09-27 17:18  董少奇  阅读(18)  评论(0)    收藏  举报