美文网首页Redis源码学习笔记
Redis源码学习基本数据结构之skiplist

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

作者: lixin_karl | 来源:发表于2019-04-29 20:14 被阅读0次

    介绍

    跳跃表(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 值为索引,对有序集元素进行排序。

    参考

    相关文章

      网友评论

        本文标题:Redis源码学习基本数据结构之skiplist

        本文链接:https://www.haomeiwen.com/subject/nkbunqtx.html