Redis源码学习笔记

Redis源码学习基本数据结构之skiplist

2019-04-29  本文已影响0人  lixin_karl

介绍

跳跃表(skiplist)是一种随机化的数据,由 William Pugh 在论文《Skip lists: a probabilistic alternative to balanced trees》中提出,这种数据结构以有序的方式在层次化的链表中保存元
素,它的效率可以和平衡树媲美——查找、删除、添加等操作都可以在对数期望时间下完成,并且比起平衡树来说,跳跃表的实现要简单直观得多。

skiplist结构

typedef struct zskiplistNode {
    sds ele;//sds 数据
    double score;//分数
    struct zskiplistNode *backward;//后退指针
    struct zskiplistLevel {
        struct zskiplistNode *forward;//前进指针
        unsigned long span;// 这个层跨越的节点数量
    } level[];//层
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;//头 尾
    unsigned long length;//节点个数
    int level;//层数最大的那个节点的层数
} zskiplist;
typedef struct {
    double min, max;
    int minex, maxex; /*minex=1就是不包括min即左边开区间 maxex同理*/
} zrangespec;

主要api及代码

zskiplist *zslCreate(void);//创建跳跃表
void zslFree(zskiplist *zsl);//释放跳跃表内存
//插入数据
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele);
//删除数据
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node);
//找到第一个在range范围内的节点
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range);
//找到最后一个在range范围内的节点
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range);
zslInsert 插入数据
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));//score必须是数值
    x = zsl->header;    //头结点
    for (i = zsl->level-1; i >= 0; i--) { //从高层到底层逐渐递进
        //如果i==zsl->level-1 rank[i] = 0 否则等于高一层的rank
        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 && 
                     //但是ele小于    
                    sdscmp(x->level[i].forward->ele,ele) < 0))) 
        {
            //更新update对应节点的排名
            rank[i] += x->level[i].span;   
            x = x->level[i].forward;        //更新x
        }
        //找到第i层的最后一个小于score的节点
        update[i] = x;
    }
    level = zslRandomLevel();// <=64 随机生成 层数
    // 如果随机生成的层数大于 这个跳跃表的层数
    if (level > zsl->level) {
        for (i = zsl->level; i < level; i++) {//更新
            rank[i] = 0;
            //新增加的高层必然指向head
            update[i] = zsl->header;
            //直接到跳跃表的tail
            update[i]->level[i].span = zsl->length;
        }
        zsl->level = level;
    }
    x = zslCreateNode(level,score,ele);//创建一个跳跃表节点
//更新update节点相关数据 以及 x的各层数据    
for (i = 0; i < level; i++) {
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;//插入数据
        //插入节点后 第i层x前后跨度分别为
        //(rank[0] - rank[i]) + 1、update[i]->level[i].span - (rank[0] - rank[i])
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;//各层跨度增加1
    }
    //update[0]应该是它前一个数据
    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
         //更新x后一个数据的backforward
        x->level[0].forward->backward = x;
    else    //如果x的紧跟着的下一个节点为空说明x是最后一个及节点
        zsl->tail = x;
    zsl->length++; //更新节点个数
    return x;
}
zslDelete 删除节点
//删除跳跃链表节点 如果此节点被删除返回1 其他返回0
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;
    x = zsl->header;//先从头开始
    for (i = zsl->level-1; i >= 0; i--) {//从高层到底层走
//下一个数据还存在
        while (x->level[i].forward && 
 //下一个数据的分数小于现在的分数 或者 分数相等 ele小
         (x->level[i].forward->score < score ||
           (x->level[i].forward->score == score &&
             sdscmp(x->level[i].forward->ele,ele) < 0)))
        {
            x = x->level[i].forward;//找到下一个节点
        }
//把每一层所指向的下一个及节点第一个大于等于 score ele的节点地址都记录下来
        update[i] = x;
    }
//如果存在要被删除的数据 x的下一个数据必然就是要被删除的数据了
    x = x->level[0].forward;
    if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
        zslDeleteNode(zsl, x, update);
        if (!node)
            zslFreeNode(x);
        else
            *node = x;
        return 1;
    }
    return 0; /* not found */
}

// zsl 跳跃表 x 将要被删除的节点 update删除x需要更新节点信息的节点列表
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
    int i;
    for (i = 0; i < zsl->level; i++) {
        //如果此节点i层下一个指向x 更新
        if (update[i]->level[i].forward == x) {
            update[i]->level[i].span += x->level[i].span - 1;
            update[i]->level[i].forward = x->level[i].forward;
        } else {//否则span-1即可
            update[i]->level[i].span -= 1;
        }
    }
    if (x->level[0].forward) {//看x是不是最后一个节点
        x->level[0].forward->backward = x->backward;
    } else {
        zsl->tail = x->backward;
    }
    //更新跳跃表的最高的层数
    while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
        zsl->level--;
    zsl->length--;//节点个数必然要减一
}

Redis中的作用

跳跃表在 Redis 的唯一作用,就是实现有序集数据类型。跳跃表将指向有序集的 score 值和 member 域的指针作为元素,并以 score 值为索引,对有序集元素进行排序。

参考

上一篇下一篇

猜你喜欢

热点阅读