写在前面
跳表的应用很多 redis zset的底层数据结构的一种就是其应用
面试被问到两次 一次不知道 一次没太讲清除 尴尬
所以专门找了redis书里的跳表进行学习
以下主要基于Redis5设计与源码分析 这本书
直接看书也可以 这篇博客是对书内容的一个解析
算是我的笔记 是我对于书内容的理解 希望能帮到你
里面有些图太丑 不要嫌弃 有些图可能因为github 加载会慢点
关于java实现跳表可以看视频:https://www.bilibili.com/video/BV1Er4y1P7k1?from=search&seid=12607675317732896430
基本思想与跳表什么样?
跳表效率堪比红黑树 但实现比红黑树简单
跳表:有序链表部分节点分层 每层都是一个有序链表
基本思想:查找时从最高层开始 如果next是空 或比key大则降一层继续查找
最终发现查找效率比有序链表高
实现过程:
如图
性质:
-
多层次构成
-
有一个header(头结点),头节点中有一个64层的结构,每层的结构包含指向本层的下个节点的指针,指向本层下个节点中间所跨越的节点个数为本层的跨度(span)见图
-
除头节点外,层数最多的节点的层高为跳跃表的高度(level),图3-3中跳跃表的高度为3。
-
每层都是一个有序链表,数据递增
-
除header节点外,一个元素在上层有序链表中出现,则它一定会在下层有序链表中出现。
-
跳跃表每层最后一个节点指向NULL,表示本层有序链表的结束
-
跳跃表拥有一个tail指针,指向跳跃表最后一个节点
-
最底层的有序链表包含所有节点,最底层的节点个数为跳跃表的长度(length)(不包括头节点),图3-3中跳跃表的长度为7。
-
每个节点包含一个后退指针,头节点和第一个节点指向NULL;其他节点指向最底层的前一个节点。
跳表每个维护了多个指向其他节点的指针?
所以在跳跃表进行查找、插入、删除操作时可以跳过一些节点,快速找到操作需要的节点。
归根结底,跳跃表是以牺牲空间的形式来达到快速查找的目的。
Redis中的跳表实现 节点与结构
跳表节点
typedef struct zskiplistNode {
sds ele; //存储字符串类型的数据
double score; //存储排序的分值
struct zskiplistNode *backward;//后退指针 指向当前节点最底层的前一个节点 才 后向前遍历跳表时使用
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[]; //柔性数组 每个节点的数组长度不同 生成跳
//跃表节点时,随机生成一个1~64的值,值越大出现的概率越低。?
//level struct zskiplistLevel类型的数组 每项包含两个元素:
(1).forward: 指向本层下一个节点 尾结点的forward指向NULL
(2) .span: forward指向的节点与本节点之间的元素个数。span值
越大,跳过的节点个数越多。
} zskiplistNode;
跳表是Redis zset的底层数据结构 则ele存储zset的成员member值
score存储成员score值 节点分值升序排序 score相同 节点按member排序
zset: ZADD key score member
跳表结构
管理节点的结构
结构体:
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
//header:跳表头节点 其level数组元素有64个
//其不存储member和score ele==NULL score==0 不计入跳表总长度 跳表多层 次的开始 初始化时 64个元素的forward指向null span==0
//tail:跳表尾结点
unsigned long length; //跳表长度 最底层的节点数(除头结点外)
int level; //跳表高度
} zskiplist;
可在O(1)的时间复杂度下,快速获取到跳跃表的头节点、尾节点、长度和高度。 因为是直接展示的
跳表如何创建
表里有节点 层高(层高决定节点中Level数组要有几个元素)需要创建节点 层高 先层高 再节点(节点中有数组level[]放之后的节点)
tail,
lenth,
level : zslRandomLevel
header : level[]:span
forward节点:ele
score
backward
ele
score
backward
tail,length,level,header ---> level[] ---> sapn , forward节点 --->ele,backward,score
1. 创建层高
决定节点的level[]数组中元素个数
节点层高最小1 最大ZSKIPLIST_MAXLEVEL Redis5中最大为64
#define ZSKIPLIST_MAXLEVEL 64
通过zslRandomLevel
随机生成1~64的值 作为新建节点的高度 值越大出现概率越低
层高确定后不会修改
代码:
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
int zslRandomLevel(void) {
int level = 1; //层高初始值为1
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) //生成随机值 取这个值的低16位作为x,当x小于0.25倍的0xFFFF时,level的值加1;
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
层高初始值为1 通过while循环 每次生成一个随机值
取这个值的低16位作为x,当x小于0.25倍的0xFFFF时,level的值加1;
否则退出while循环。
最终返回level和ZSKIPLIST_MAXLEVEL两者中的最小值
2. 创建节点
首先分配内存 节点里有ele,score,zskiplistLevel
zskiplistNode *zn =
zmalloc(sizeof(*zn