Redis跳跃表
Posted 原创工厂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis跳跃表相关的知识,希望对你有一定的参考价值。
Redis的有序集合ZSet是通过跳跃表实现的,那么什么是跳跃表?为何要采用跳跃表作为ZSet的数据结构,其性能如何?
跳跃表由zskiplist和zskiplistNode组成。每个跳跃表节点的层数都是1-32之间的随机数(每创建一个节点,程序会随机生成一个[1-32]作为level数组的大小)。
同一个跳跃表中,多个节点可以包含相同的分值,但节点的成员对象是唯一的;按分值排序,若分值相同就按成员对象大小排序。
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zskiplistNode {
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level [];
struct zskiplistNode *backward;
double score;
robj *obj;
} zskiplistNode;
一般地单链表,查找某个数据的时间复杂度是o(n), 如何提高效率?
如图所示,可以建立索引层的方法。将每2个节点抽取一个节点到上一层,被抽取出来的称之为【索引层】。
此时相当于HashMap存储了一个key值,其value是对应原始连接中的2个节点组成的List。
第二级索引:把第一级索引的2个节点再抽取出来组成二级索引,此时查找遍历的数据更少了。
如此反复,当节点数量非常多的时候,这种添加索引的方式会使得查询效率提高地非常明显。
这样,由索引和原始链表构成的一种数据结构就是跳跃表。
有n个节点的链表,如果每2个链表组成一个索引,那么,
假设最后一级索引的个数为2,则h+1 = logn,算上最底层的一层链表,跳表的高度H=logn.
每一级只需要遍历2个节点即可,因此时间复杂度是
o(logn)。
为什么是2个?因为我们是每两个结点提取一个结点建立索引,最高一级索引只有两个结点,然后下一层索引比上一层索引两个结点之间增加了一个结点,也就是上一层索引两结点的中值,看到这里是不是想起来我们前边讲过的二分查找,每次我们只需要判断要找的值在不在当前结点和下一个结点之间即可。
n/2 + n/4 + .... + 8 + 4 + 2 = n-2。
如果想要减少索引所占的内存空间,可以考虑每3个或者5个抽取一个索引。
跳表的查询的时间复杂度为 O(logn),因为找到位置之后插入和删除的时间复杂度很低,为 O(1),所以最终插入和删除的时间复杂度也为 O(longn)。
如果我们不停的向跳表中插入元素,就可能会造成两个索引点之间的结点过多的情况。结点过多的话,我们建立索引的优势也就没有了。所以我们需要维护索引与原始链表的大小平衡,也就是结点增多了,索引也相应增加,避免出现两个索引之间结点过多的情况,查找效率降低。
跳表是通过一个随机函数来维护这个平衡的,
当我们向跳表中插入数据的的时候,我们可以选择同时把这个数据插入到索引里,那我们插入到哪一级的索引呢,这就需要随机函数,来决定我们插入到哪一级的索引中。
删除操作的话,如果这个结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到要删除结点的前驱结点,然后通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点。当然,如果我们用的是双向链表,就不需要考虑这个问题了。
以上是关于Redis跳跃表的主要内容,如果未能解决你的问题,请参考以下文章
Redis跳跃表
Redis源码解读——跳跃表
Redis源码解读——跳跃表
Redis中的跳跃表
redis源码跳跃表(zskiplist)
Redis底层解析跳跃表