redis学习系列(六)--redis基础跳跃表的构造
看redis的时候第一次看见跳跃表这种数据结构,说实话,之前还真不知道有这么个结构,哎。。。。。。。。。。。
跳跃表其实也是一个链表,不过子啊实现上有点区别,也是由一个一个的Node组成,zskiplistNode是组成,zsklist则是保存了跳跃表节点的相关信息,例如节点的数量,头结点指针,尾节点指针。
zskiplist
最左边的就是zskiplist结构:
header: 指向表头节点
tail: 指向表尾节点
lever: 记录跳跃表内,层级最大的那个节点的层级(表头节点的层级不计算在内)
length:记录跳跃表的长度,就是跳跃表包含的节点的数量(表头节点不在计算内)
zskiplistNode
右边的是四个zskiplistNode结构:
层(lever):节点中用L1,L2,L3等字样标记各个层,L1代表第一层,L2代表第二层,每层有两个属性:前进指针和跨度
前进指针:用于访问表尾方向的其他节点
跨度:记录了前进指针所指向节点和当前节点的距离
具体看上图:连线上带有数字的箭头就代表前进指针,而数字就是跨度
程序在从表头遍历想表尾遍历时,访问会沿着层的前进指针进行
后退指针:节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点,因此它在从表尾向表头遍历时使用。
分值:节点中保存的值 1.0 ,2.0, 3.0
成员对象:保存的成员对象,o1,o2,o3
zskiplistNode的构造
typedef struct zskiplistNode { // 后退指针 struct zskiplistNode *backward; // 分值 double score; // 成员对象 robj *obj; // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *forward; // 跨度 unsigned int span; } level[]; } zskiplistNode;层
跳跃表节点的lever数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序就是通过这些指针实现快速访问
前进指针:
每一层都会有一个前进指针,表名遍历的方向;
图5-3中用虚线表示出从表头向表尾的方向,遍历所有节点的路径。
1.迭代程序首先访问跳跃表的第一个节点(表头),然后从L4的前进指针移动到表中的第二个节点
2.在第二个节点时,程序沿着第二层的前进指针移动到表中的第三个节点
3.在第三个节点时,程序同样沿着第二层的前进指针移动到表中的第四个节点。
4.当程序在第四个节点的前进指针移动时,会碰到一个null,此时已经到达了跳跃表的表尾,
跨度
层的跨度用于记录两个节点之间的距离
1.两个节点之间的跨度越大,说明它们相隔的越远
2.指向null的所有前进指针的跨度为0,因为它们没有连上任何节点。
跨度是计算排位的: 排位是指在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是跳跃表中的排位。
下图例子中,虚线标注了在跳跃表中查找分值为3.0,成员对象为o3的节点时,沿途经历的层,由图可知只经历过一层,跨度为3
后退指针
bw属性,用于表尾向表头访问节点时,与前进指针不同的是,每个节点只有一个后退指针,所以每次只能后退一个节点
图5-6表示的是从tail节点从表尾向表头遍历,直至遇到null节点,结束
分值和成员
节点的分值都是按照分值大小从小到大来排序
节点的成员对象是一个指针,指向一个字符串对象,而字符串保存着一个SDS值。
在同一个跳跃表中,保存的成员对象是唯一的,但是分值确实可以相同的,分值相同的节点将按照在字典序中来排序,成员对象小的节点排在前面。
zskiplist
结构的定义如下:
typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
int level;
} zskiplist