「这是我参与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 记录了所有节点中层级最高的节点的层级
跳表实现
具体来说,跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。
如果我们要在链表中查找 33 这个元素,只能从头开始遍历链表,查找 6 次,直到找到 33为止。此时,复杂度是 O(N),查找效率很低。
为了提高查找速度,我们来增加一级索引:从第一个元素开始,每两个元素选一个出来作为索引。这些索引再通过指针指向原始的链表。例如,从前两个元素中抽取元素 1 作为一级索引,从第三、四个元素中抽取元素 11 作为一级索引。此时,我们只需要 4 次查找就能定位到元素 33 了。
如果我们还想再快,可以再增加二级索引:从一级索引中,再抽取部分元素作为二级索引。例如,从一级索引中抽取 1、27、100 作为二级索引,二级索引指向一级索引。这样,我们只需要 3 次查找,就能定位到元素 33 了。
可以看到,跳表就是根据总元素数量分出多级索引来加快查询速度。需要注意的是链表需要是有序的。
跳表搜索的时间复杂度平均 O(logN),最坏O(N),由于增加了多级索引,所以跳表占用了更多的空间,空间复杂度O(2N),即O(N)。但是跳表保留了链表的优点,优化了查询速度。