跳表:是为一个有序的链表建立多级索引的数据结构叫做跳表。redis中zset数据量大时底层数据结构使用跳表。
redis中定时器使用的是无序的双向链表。时间复杂度为O(N),redis作者在定时器备注了可以适当优化的措施:
1 尽可能让数据有序,2 可以使用跳表完成
回归正题,跳表:
其他地方借图一张如下:
跳表是基于有序链表所实现的,为了实现快速的查找,在做节点比较的时候跳过一些节点以达到快速查找的目的,是一种空间换时间的思想。
如上图,此跳表总共三层(level = 3),redis中zset在数据量比较大时,采用的即是跳表实现 其最大层数是32,再插入节点的时候随机生成层数,最大不超过32;
如上图为例,头节点不保存数据,按照上图分析插入操作:
插入之前要先从上层到下层临时保存每一层的当前节点,然后随即生成新的newlevel,如果newlevel大于level,则对于(level-newlevel)层进行初始化头结点 。
插入14前,没有数据只有头结点 ,当前level默认是1,用temp[0]临时保存节点,
while循环是找到下一个节点,比如插入34时,while之后level1 层 x节点就是23;插入50时,while循环之后 level1层temp[0] = 43,level2层 temp[1]=34,level3层 temp[2]=14;
保存完临时节点后,随机生成新的层数,
14插入时随机层数是3,大于之前默认的level =1;对于level2 和level3就要进行头结点赋值,temp[1]=header;temp[2]=header;
然后进行新节点的插入,之前遍历都是从高层向底层扩展,插入操作要从底层向高层扩展。
插入操作之后,形成了temp[0]->next = 节点14,temp[1]->next = 节点14,temp[2]->next = 节点14即header[0]->next = 节点14,header[1]->next = 节点14,header[2]->next = 节点14;
14节点插入之后,链表长度+1(单指最底层的链表);
接着插入节点23,临时变量temp值为temp[0]= 节点14,temp[1] = 节点14,temp[2] = 节点14; 随机新的newlevel =1,说明23 只出现在level1层,进行插入后temp[0]->next = 节点23,其他两个节点不变。
插入节点34后,各层链表为
header[2]->14
header[1]->14->34
header[0]->14->23->34
以上就是插入的大致逻辑分析。
删除操作类似,先从上层开始向后遍历然后向下,删除时从下层开始向上操作。
跳表实现定时器demo源码地址:跳表实现定时器demo
红黑树:一颗节点非红即黑的平衡二叉树。epoll底层使用红黑树。
红黑树插入 查询,删除等基本操作时间复杂度为O(lgn),跳表搜索插入删除操作时间复杂度接近O(lgn),最坏情况下变成O(n)。
做范围查找的时候 ,平衡树比跳表操作要复杂,平衡树上,找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其他不超过大值的节点,相对而言,跳表的范围查询就比较简单,只要找到小值,在第一层链表进行若干层遍历就行。
从算法实现难度讲,跳表比红黑树要简单的多。
红黑树也是实现定时器的数据结构之一,具体实现不再详叙。