首页 > 数据库 > redis底层数据结构之跳表(skiplist)

redis底层数据结构之跳表(skiplist)

时间:2022-12-17 18:12:56浏览次数:50  
标签:level redis update skiplist zsl 跳表 forward 节点

跳表(跳跃表, skiplist)

跳跃表(skiplist)是用于有序元素序列快速搜索查找的数据结构,跳表是一个随机化的数据结构,实质是一种可以进行二分查找的、具有层次结构的有序链表

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找,平均期望的查找、插入、删除时间复杂度都是O(logn),同时支持范围查询

 

具有如下性质:
1) 每个节点由多层组成,排列顺序为由高层到底层

2) 每一层都是一个有序链表

3) 最底层的链表包含了所有的元素

4) 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现

5) 每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点

6) 分数(score)允许重复,即key是允许重复的

 

1 zskiplist结构

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

其中:

header: 表头节点,没有存储实际的数据,而是一个空的,初始化层级数为ZSKIPLIST_MAXLEVEL值(默认32)的节点

tail: 表尾节点

length: 节点数量

level: 最大层级,表头节点层数不计

 

2 zskiplistNode结构

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

其中:

ele:节点对象,即节点数据

score:当前节点值对应的分值,用于排序,按分值从小到大来排序,各个节点对象必须是唯一的, 但是多个节点保存的分值却可以是相同的, 分值相同的节点按照成员对象值从小到大排序

backward:当前节点的上一个节点

level:表示层数

  forward:同一层的下一个节点

  span:跨度,当前节点到forward指向的节点跨越了多少个节点

 

3 skiplist示意图

 

 

 

4 skiplist插入节点

步骤如下:

1) 找到每一层新节点要插入的位置(update):从高层到低层遍历skiplist,找到每一层小于新节点score的最大的节点,如果节点score相等,则比较ele

2) 随机分配一个层数(level),如果层数比skiplist的层数大,增加skiplist的层数并修改新加层数的span

3) 插入新节点(x):新节点每一层(x->level[i]) 的forward修改为每一层插入位置(update[i]->level[i]) 的forward,每一层插入插入位置的forward修改为新节点(顺序不能反),更新新节点(x),插入位置节点(update[i]->level[i]) 的每一层的span

4) 修改更高的level的span

5) 修改新节点的backward(x->backward)

6) 修改新节点最低层后一个节点(x->level[0].forward)的backward:如果新节点的forward不为null,则新节点后一个节点的backward为新节点;否则新节点是skiplist的尾结点

7) 修改skiplist的节点数量

 

src/t_zset.c文件对应的源码如下:

/* Insert a new node in the skiplist. Assumes the element does not already
 * exist (up to the caller to enforce that). The skiplist takes ownership
 * of the passed SDS string 'ele'. */
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned long rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));
    x = zsl->header;
    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[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);
    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 */
        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;
}

 

 

 

5 skiplist删除节点

步骤如下:

1) 找到每一层要删除节点(x) 的前一个节点(update):从高层到低层遍历skiplist,每一层都找到小于新节点score的最大的节点,如果节点score相等,则比较ele

2) 删除节点,更新每一层要删除节点前一个节点(udpate[i]) 的span和forward

3) 修改要删除节点后一个节点最低层([x->level[0].forward->backward]) 的backward:如果删除节点最低层的forward不为null,则要删除节点后一个节点最低层的backward为要删除节点的backward,否则要删除节点后一个节点是skiplist的尾结点

4) 修改skiplist的层数level

5) 修改skiplist的节点个数

 

src/t_zset.c文件对应的源码如下:

/* Internal function used by zslDelete, zslDeleteRangeByScore and
 * zslDeleteRangeByRank. */
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;
            update[i]->level[i].forward = x->level[i].forward;
        } else {
            update[i]->level[i].span -= 1;
        }
    }
    if (x->level[0].forward) {
        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--;
}

 

标签:level,redis,update,skiplist,zsl,跳表,forward,节点
From: https://www.cnblogs.com/junffzhou/p/16972095.html

相关文章

  • redis底层数据结构之整数集合(intset)
    整数集合(intset)当一个集合只包含整数值元素,并且这个集合的元素数量不多时,redis会使用整数集合(intset)作为集合键的底层实现整数集合用于保存整数值的集合抽象数据类型......
  • redis之五种基本数据类型
    五种基本数据类型redis存储任何类型的数据都是以key-value形式保存,并且所有的key都是字符串,所以讨论基础数据结构都是基于value的数据类型常见的5种数据类型是:String、Li......
  • redis底层数据结构之简单动态字符串(SDS)
    简单动态字符串(simpledynamicstring,SDS)redis使用C语言编写的,但是redis的字符串却不是C语言中的字符串(以空字符'\0'结尾的字符数组),redis定义了一种简单动态字符串(s......
  • redis底层数据结构之双向链表(linkedlist)
    双向链表(linkedlist)redis的双向链表(linkedlist)是基于链表的一种数据结构链表是一种常见的基础数据结构,是一种非顺序存储数据的线性表,在每一个节点里存储了下一个节点......
  • redis底层数据结构之压缩列表(ziplist)
    压缩列表(ziplist)压缩列表(ziplist)是redis为了节约内存而开发的,由连续内存块组成的顺序型数据结构,适用于长度较小的值存取的效率高,内存占用小,但由于内存是连续的,在修......
  • redis底层数据结构之快速列表(quicklist)
    快速列表(quicklist)redis3.2版本之前,List类型数据使用的底层数据结构是压缩列表(ziplist)或双向链表(linkedlist),当列表元素个数比较少并且每个元素占用空间比较小时使......
  • redis常用命令之string&list
    redis常用操做stringkey操作string<key:value>setnamejohngetnamelistsetnx<keyvalue>setnxgendermale(分布式锁)getgendersetnxgoods_1111delgoods_1ge......
  • redis分布式锁
    redis分布式锁C#集合线程问题:https://www.cnblogs.com/Clingingboy/archive/2010/12/06/1897534.htmlC#多线程安全集合类:https://www.cnblogs.com/Darius0821/p/16967......
  • windows下安装和配置Redis
    一、下载windows版本的Redis    Redis官方提供的是Linux安装版的,并没有Windows版本的Redis,为了学习Redis总不能去跑个虚拟机来运行吧,所以在GitHub中有人发布了Windows......
  • 一个Redis dump文件的简要分析过程
    摘要遇到一个老大难的问题.让帮忙分析一下一个Redis的dump文件.虽然之前写过了rdb和rdr的文档但是感觉大家都喜欢拿来主义.没办法.今天继续进行深入一点的分析.原......