跳跃表结点由redis.h/zskiplistNode结构定义
typedef struct zskiplistNode{
//后退指针
struct zskiplistNode *backward;
//分值key
double score;
//成员对象value
robj *obj;
//层(也是一个结构体,不过是一个数组)
struct zskiplistLevel{
//前进指针
struct zskiplistNode *forward;
//跨度
unsigned int span;
}l
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
evel[];
}zskiplistNode;
层
跳跃表结点的level数组可以包含多个元素(不过越前面的结点,包含的元素一般会更多,因为要保证符合跳跃表的要求,即每一层的索引链表必须是下一层的索引链表的子集),一般来说,层越高,找元素的效率就越快。
每次创建一个结点的时候,redis会根据幂次定律(越大的数出现的机率越小)随机生成一个介于1和32之间的值,作为Level数组的大小(也就是这个结点的所拥有的索引高度,即上面有多少个索引链表会可以找到他)
前进指针
每个层里面的元素都有一个指向表尾的指针,用来找到下一个结点(对比key后,发现小了,要往前找,如果碰到NULL,就代表元素不存在)。
跨度
记录两个结点间的距离
-
两个结点之间的跨度越大,相距就越远
-
指向NULL的前进指针的跨度都为0
其实跨度的用途是用来计算结点的排位(rank)的,也就是结点的位置,排在最底层的第几个,找到结点的路径遇到的所有层的所有跨度加起来就是结点的排位。
后退指针
每个结点都有后退指针,用来从表尾遍历到表头的。
分值和成员
结点的分值就是存储键值对的value值,是一个double类型的浮点数,结点的顺序是按照value的大小来进行排序的(所以value必须可以解析为浮点型,而且必须唯一,如果重复的话会发生替换)
结点的成员对象是一个指针,指向的对象是一个sds对象(之前提到的SDS字符串),用来储存键值对的key值。
跳跃表
多个跳跃结点就可以形成跳跃表,并且仅仅通过一个zskiplist结构来持有这些跳跃结点。
typedef struct zskiplist{
//表头结点和表尾结点
struct skiplistNode *header,*tail;
//表中结点数量
unsigned long length;
//表中层数最大结点的层数(即拥有的索引链表数量)
int level;
}zskiplist;
header和tail指针分别指向跳跃表的表头和表尾结点,通过这两个指针,程序找到第一个结点和最后一个结点的时间复杂度都为 O ( 1 ) O(1) O(1),并且得到跳跃表的结点个数或者长度,直接返回length即可,时间复杂度也为 O ( 1 ) O(1) O(1)。
注意,LEVEL属性是不包括傀儡结点的,傀儡结点里面的数组有很多元素结点没有形成链表,即跨度为0
重点
- 跳跃表是有序集合的底层实现之一
- Redis的跳跃表由zskiplist和zskiplistNode组成,前者保存跳跃表信息,后者是跳跃表结点
- 每个跳跃表结点的层高为1~32
- 同一个跳跃表中,多个结点可以包含相同的分值(即score可以相同,score必须可以转化为浮点型!),但每个结点的成员对象(一个sds字符串)必须唯一(member必须唯一,出现重复会重置)
- 跳跃表的结点按照分值进行排序,如果出现分值相同,就按照成员对象的大小进行排序