Redis底层数据结构之跳跃表

Redis底层数据结构之跳跃表

1.引言

跳跃表由 William Pugh 在1990年发明,是一种有序的数据结构,类似于平衡树、红黑树这样的数据结构,能够维护一个有序的列表,方便查找。跳跃表支持平均为O(logN)的时间复杂度查找,这与平衡树大体相当,那么为什么Redis的作者选用跳跃表来实现有序集合呢?我们从以下几点说明

  • 插入和删除:平衡树的调整操作都需要从引起不平衡的节点开始,向上调整,通过一系列的左右旋转操作来达到平衡,但是跳跃表只需要更改相邻节点的指针即可,这一点在后面的实现说明中会体现到

  • 内存占用:平衡树的每个节点几乎都要占用两个指针,而跳跃表的指针数目取决于全局参数P,指针个数N = (1-p)/4,Redis使用P=1/4,所以平均占用1.33个指针

  • 实现难度:跳跃表的实现难度要比平衡树简单的多,并且查找时间复杂度大体相当,均为O(logN)

1.普通跳跃表性质

首先给出跳跃表的一个直观表示:

  • 跳跃表由很多层构成,每一层都是一个有序链表

  • 最底层包含了所有的元素,如果一个元素出现的level i中那么在level i之下的链表中也会出现

  • 每个节点包含两个指针,一个指向同一层链表的下一个元素

普通跳跃表实现代码(加注解):

    #include<stdio.h>
    #include<stdlib.h>
    #define MAX_LEVEL 10
    typedef struct skipNode* Node;
    struct skipNode{
        int key;
        int value;
        Node forward[1];
    };
    typedef struct skiplist{
        Node header; 
        int level;
    }skiplist;

    Node createNode(int level,int key,int value){
        //使用malloc分配level个连续空间,包括存放节点的key和value的skipNode空间 
        skipNode * node = (Node) malloc(sizeof(skipNode) + level * sizeof(Node));
        node->key = key;
        node->value = value;
        return node;
    }

    skiplist * createSkiplist(){
        skiplist *list = (skiplist*)malloc(sizeof(skiplist));
        list->level = 0;
        list->header = createNode(MAX_LEVEL,0,0);
        /*
            levelN      forward[N] = NULL
            levelN-1        forward[N-1] = NULL
            levelN-2        forward[N-2] = NULL
                             . 
            level0      forward[0] = NULL
            header------>
        */ 
        for(int i = 0; i < MAX_LEVEL; i++){
            list->header->forward[i] = NULL;
        }
        return list;
    }

    int randomLevel(){  
        int k=1;  
        while (rand()%2)    
            k++;    
        k=(k<MAX_LEVEL)?k:MAX_LEVEL;  
        return k;    
    }  

    /*
      插入元素 
     */
    bool insertElement(skiplist* list , int key , int value){
        //使用一个数组保存比key大的节点位置
        skipNode * update[MAX_LEVEL];
        //查找原始key要插入的位置,从高层level乡下查找
        int level = list->level;
        skipNode* head = list->header;
        skipNode* next;
        for(int i = level - 1; i >= 0; i--){
            while((next = head->forward[i])&& (next->key < key)){
                head = next;
            }
            update[i] = head;
        }
        // key值已经存在
        if(next && next->key == key){
            // 覆盖掉原来的value 
            head->value = value;
            return false;
        }
         //随机生成新结点的层数  
        int nlevel = randomLevel();
        if(nlevel > list->level){
            nlevel = ++list->level;  
            update[level] = list->header;  
        }
        // 申请新节点的空间
        Node newNode = createNode(nlevel,key,value);
        // 逐层调整指针指向
        for(int i = 0 ; i < nlevel ; i++){
            newNode->forward[i] = update[i]->forward[i];
            update[i]->forward[i] = newNode;
        } 
        return true;
    }

    /*
      搜索指定key的value
    */  
    skipNode* search(skiplist* list,int key)  
    {  
        skipNode *p,*q=NULL;  
        p = list->header;  
        //从最高层开始搜  
        int k = list->level;  
        for(int i = k-1; i >= 0; i--){  
            while((q = p->forward[i]) && (q->key <= key)) {  
                if(q->key == key){  
                    return q;  
                }  
                p=q;  
            }  
        }  
        return NULL;  
    }

    /**
      删除指定节点的值 
    **/
    bool deleteElement(skiplist* list , int key, int value){
        skipNode* update[MAX_LEVEL];
        skipNode* x = list->header;
        int level = list->level;
        for(int i = level - 1 ; i >= 0 ; i--){
            while(x->forward[i]->key < key){
                x = x->forward[i]; 
            }
            update[i] = x;
        }
        x = x->forward[0];
        if(x->key != key){
            // 节点不存在直接返回 
            return false;
        }else{
            for(int i = 0 ; i < list->level ; i++){
                if(update[i]->forward[i] == x){
                    update[i]->forward[i] = x->forward[i];    
                }
            }
            free(x);
            for(int i = list->level-1 ; i >= 0 ; i--){
                if(list->header->forward[i] == NULL){    
                        list->level--;
                    }
            }
        }
        return true; 
    }

2. Redis中跳跃表的实现

zskiplistNode的定义

typedef struct zskiplistNode {
        robj *obj;
        double score;
        struct zskiplistNode *backward;
        struct zskiplistLevel {
            struct zskiplistNode *forward;
            unsigned int span;
        } level[];
    } zskiplistNode;

zskiplist定义

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

图解:

性质说明:

  • 多个节点可以包含相同的score,但是每个节点的成员对象必须是唯一的

  • 跳跃表按照score的大小进行从小到大排序,当分数一样是,节点按照成员对象的大小进行排序

具体的实现代码在T_zset.c文件中

首先是创建和初始化操作:

//创建节点
    zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
        zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
        zn->score = score;
        zn->obj = obj;
        return zn;
    }

    zskiplist *zslCreate(void) {
        int j;
        zskiplist *zsl;
        //创建zskiplist
        zsl = zmalloc(sizeof(*zsl));
        zsl->level = 1;
        zsl->length = 0;
        //创建header节点,ZSKIPLIST_MAXLEVEL=32,默认层数最大为32
        zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
        // 初始化
        for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
            zsl->header->level[j].forward = NULL;
            zsl->header->level[j].span = 0;
        }
        zsl->header->backward = NULL;
        zsl->tail = NULL;
        return zsl;
    }

在看插入函数zslInsert:

zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
        // 使用Update数组记录要插入的位置
        zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
        // 使用rank数组记录每层的插入位置所跨越的节点个数
        unsigned int rank[ZSKIPLIST_MAXLEVEL];
        int i, level;

        redisAssert(!isnan(score));
        x = zsl->header;
        // 从上往下遍历
        for (i = zsl->level-1; i >= 0; i--) {
            /* 每一层遍历开始初始化当前层跨越的节点数目,也就等于上一层终止查询时候跨越的节点数目*/
            rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
            /* 按照score的大小进行从小到大查找,当分数一样是,节点按照成员对象的大小进行查找
             * 找到第一个不满足条件的节点,即为新节点要插入的位置的前驱,用update记录
             */
            while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
                    compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
                /** rank 值在初始条件下逐步增加**/
                rank[i] += x->level[i].span;
                x = x->level[i].forward;
            }
            update[i] = x;
        }

        // 随机生成待插入节点的层数
        level = zslRandomLevel();
        // 如果随机生成的层数比当前跳跃表的层数大,则使用header代表高出部分的前驱
        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,obj);
        // 更新新插入节点的指针指向
        for (i = 0; i < level; i++) {
            x->level[i].forward = update[i]->level[i].forward;
            update[i]->level[i].forward = x;

            /* 使用原来记录的rank数组更新新节点的跨度距离 */
            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;
    }

删除函数zslDelete:

/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
    int i;
    // 从下往上逐层更新指针指向和跨度
    for (i = 0; i < zsl->level; i++) {
        if (update[i]->level[i].forward == x) {
            // 更新跨度,
            update[i]->level[i].span += x->level[i].span - 1;
            // 更新forward指向,
            update[i]->level[i].forward = x->level[i].forward;
        } else {
            update[i]->level[i].span -= 1;
        }
    }
    // 更新backward指针
    if (x->level[0].forward) {
        x->level[0].forward->backward = x->backward;
    } else {
        zsl->tail = x->backward;
    }
    // 更新level的新高度
    while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
        zsl->level--;
    zsl->length--;
}

/* Delete an element with matching score/object from the skiplist. */
int zslDelete(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;

    x = zsl->header;
    // 和插入操作一样,首先获得删除节点的前驱位置,使用update数组记录
    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 &&
                compareStringObjects(x->level[i].forward->obj,obj) < 0)))
            x = x->level[i].forward;
        update[i] = x;
    }
    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. */
    x = x->level[0].forward;
    // 如果找到的节点x是要删除的节点,则执行删除操作,释放删除节点的内存
    if (x && score == x->score && equalStringObjects(x->obj,obj)) {
        zslDeleteNode(zsl, x, update);
        zslFreeNode(x);
        return 1;
    }
    return 0; /* not found */
}

除了基本的删除和插入操作外,Redis跳跃表还提供很多操作,包括获得指定节点在表中的排位zslGetRank函数,获得满足一定条件的排位等操作,从这里可以看出Redis跳跃表中设置跨度字段的好处,能够快速的计算排位,具体的实现函数这里就不一一介绍了,感兴趣的朋友可以参照源码一探究竟。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis是一个开源的内存数据库,它使用了多种数据结构来存储不同类型的数据。下面是几种常见的Redis底层数据结构的详解: 1. 字符串(String):字符串是Redis中最基本的数据结构。它可以存储任意类型的数据,包括数字、文本等。字符串在Redis中以字节数组的形式存储,可以通过键访问和修改。 2. 列(List):列是一个有序的字符串集合,可以在列的两端进行插入、删除和获取操作。Redis使用双向链来实现列数据结构,它支持快速插入和删除操作。 3. 哈希(Hash):哈希是一种键值对的集合。在Redis中,哈希可以存储多个字段和对应的值,类似于关联数组或者字典。哈希在内部使用哈希来实现,可以快速查找和修改字段值。 4. 集合(Set):集合是一组唯一且无序的字符串集合。Redis使用哈希来实现集合数据结构,它支持添加、删除和判断元素是否存在等操作。 5. 有序集合(Sorted Set):有序集合是一组唯一且有序的字符串集合。在Redis中,每个元素都会关联一个分数,通过分数可以对元素进行排序。有序集合的实现使用了跳跃和哈希两种数据结构,它支持添加、删除、修改和范围查询等操作。 这些数据结构底层实现都是高效的,并且支持丰富的操作。Redis数据结构灵活性较高,能够满足不同类型的数据存储需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值