Redis系列之底层数据结构具体实现(跳跃列表)

166 阅读3分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

跳表是Redis的有序集合类型的底层实现之一,另外一种是压缩列表。

压缩列表实现

快速列表实现

跳跃列表

跳跃列表(skiplist)简称跳表

跳表属于链表的一种,通过链表的结构我们可以知道链表在插入和更改方面很便利,但是伴随着查询操作,需要遍历整个链表,复杂度为O(N),跳表就是在链表的基础上,在查询方面做了一些优化,使查询速度更快了一些。

zskiplistNode

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

} zskiplistNode;

robj 表示 member 对象。

score 表示分值,用于排序和范围查找。

backward 表示后退指针。

level 是一个柔性数组,它表示节点的层级,每层都有一个前进指针 forward,用于指向相同层级指向表尾方向的下一个节点。

span 这个层跨越的节点数量。

zskiplist

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

} zskiplist;

header 和 tail 指针分别指向表头和表尾节点

length 记录了节点数量

level 记录了所有节点中层级最高的节点的层级

跳表实现

具体来说,跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。

Snipaste_2021-11-06_22-29-44.png

如果我们要在链表中查找 33 这个元素,只能从头开始遍历链表,查找 6 次,直到找到 33为止。此时,复杂度是 O(N),查找效率很低。

为了提高查找速度,我们来增加一级索引:从第一个元素开始,每两个元素选一个出来作为索引。这些索引再通过指针指向原始的链表。例如,从前两个元素中抽取元素 1 作为一级索引,从第三、四个元素中抽取元素 11 作为一级索引。此时,我们只需要 4 次查找就能定位到元素 33 了。

如果我们还想再快,可以再增加二级索引:从一级索引中,再抽取部分元素作为二级索引。例如,从一级索引中抽取 1、27、100 作为二级索引,二级索引指向一级索引。这样,我们只需要 3 次查找,就能定位到元素 33 了。

可以看到,跳表就是根据总元素数量分出多级索引来加快查询速度。需要注意的是链表需要是有序的。

跳表搜索的时间复杂度平均 O(logN),最坏O(N),由于增加了多级索引,所以跳表占用了更多的空间,空间复杂度O(2N),即O(N)。但是跳表保留了链表的优点,优化了查询速度。