【Redi设计与实现】第五章:跳跃表

简介: 【Redi设计与实现】第五章:跳跃表

5.1 跳跃表的实现

Redis的跳跃表由 zskiplistNodezskiplist 两个结构定义。

其中,zskiplistNode 用于表示跳跃节点,而 zskiplist 结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等。

图5-1展示了一个跳跃表实例,位于图片最左边的是 zskiplist 结构,该结构包含一下属性:

  • header:指向跳跃表的头节点
  • tail:指向跳跃表的尾节点
  • level:记录目前跳跃表内,层数最大的那个节点的层数
  • length:记录跳跃表的长度

位于 zskiplist 结构右方的是四个 zskiplistNode 结构,该结构包含以下属性:

  • 层:节点中使用L1、L2 、L3等字样标记节点的每个层,L1代表第一层,以此类推。每个层都含有2个属性:前进指针和跨度。前进指针用于访问表尾方向的其他节点,而跨度记录了前进指针和跨度所指节点和当前节点的距离。如图5-1所示,连线上带有数字的箭头就代表了前进指针,而那个数字就是跨度。
  • 后退指针:节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。
  • 分值:各个节点的1.0、2.0、3.0是节点保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。
  • 成员对象:各个节点的o1、o2、o3是节点所保存的对象。

注意表头节点和其他节点的构造是一样的:表头节点也有后退指针、分值、成员对象,不过表头节点的这些属性不会被用到,所以图中省略了这些部分,只显示了表头节点的各个层。

5.1.1 跳跃表节点

跳跃表节点的实现由 zskiplistNode 结构定义:

typedef struct zskiplistNode {
  // 层
  struct zskiplistLevel {
    // 前进指针
    struct zskiplistNode *forward;
    // 跨度
    unsigned int span;
  } level[];
  // 后退指针
  struct zskiplistNode *backward;
  // 分值
  double score;
  // 成员对象
  robj *obj;
} zskiplistNode;
5.1.1.1 层

跳跃表的层数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快。

每次创建一个新跳跃节点的时候,程序都会根据幂次定律(越大的数出现的概率越小)随机生成一个介于1~32之间的值作为level数组的大小,这个大小就是层的高度。

5.1.1.2 前进指针

每个层都有一个指向表尾方向的前进指针(level[i].forward属性),用于从表头向表尾方向访问节点。

  • 程序首先访问第一个节点(表头),然后从第四层的前进指针移动到表中的第二个节点。
  • 在第二个节点时,程序沿着第二层的前进指针移动到列表中的第三个节点。
  • 在第三个节点时,程序同样沿着第二层的前进指针到达表中的第四个节点
  • 当程序到达第四个节点的时候,会碰到一个NULL,程序知道已经到达了跳跃表的表尾,于是结束这次遍历。
5.1.1.3 跨度

层的跨度主要记录两个节点之间的距离:

  • 两个节点之间的跨度越大,他们相距的就越远
  • 指向NULL的所有前进指针的跨度都为0,因为他们没有连接任何节点。

第一眼看上去,很容易以为跨度和遍历操作有关,但实际上不是这样的。

遍历我们只需要使用前进指针就可以完成了,跨度实际上是用来计算排名的:在查找某个节点的过程中,将沿途访问过的所有层的跨度加起来,得到的结果就是目标节点在跳跃表的排位。

5.1.1.4 后退指针

节点的后退指针用于从表尾向表头方向访问节点:前进指针的话,可以一次性跳过多个节点,而我们的后退指针,一次只能回退一个节点。

5.1.1.5 分值和成员

节点的分值(score属性)是一个double类型的浮点数,跳跃表中的所有节点都按照分值从小到大进行排序。

节点的成员对象(obj属性)是一个指针,指向一个字符串对象,而字符串对象则保存着一个SDS值。

在同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是各个节点保存的分值却可以是相同的:分值相同的节点将按照成员对象在字典序中的大小来进行排序,成员较小的节点会排在前面(靠近表头的地方),而成员对象较大的节点则会排在后面(靠近表尾的方向)。

5.1.2 跳跃表

通过一个 zskiplist 结构来持有这些节点,程序可以方便的对整个跳跃表进行处理,比如:快速访问表头节点和表尾节点,或者快速的获取跳跃表的长度。

zskiplist 结构定义如下:

typedef struct zskiplist {
  // 表头节点和表尾节点
  struct skiplistNode *header,*tail;
  // 表示节点的数量
  unsigned long length;
  // 表中层数最大的节点的层数
  int level; 
} zskiplist;

headertail 指针分别指向跳跃表的表头和表尾节点,通过这两个指针,我们可以通过O(1)的时间复杂度来获取表头和表尾。

通过使用 length 属性来记录节点的数量,程序可以在O(1)的复杂度返回跳跃表的长度。

level 属性用于在O(1)的时间获取跳跃表层数最大的那个节点的层数量(不算表头的)。

5.2 跳跃表API

5.3 重点回顾

  • 跳跃表是有序集合的底层实现之一。
  • Redis 的跳跃表实现由 zskiplistzskiplistNode 两个结构构成,其中 zskiplist 属性保存跳跃表的信息(比如表头节点、表尾节点、长度),而 zskiplistNode 用于表示跳跃表节点。
  • 每个跳跃表节点的层高都是 1~32 之间的随机数。
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但不能包含相同的对象。
  • 跳跃表节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。


相关文章
|
JSON NoSQL Redis
redis-full-check校验工具
redis-full-check是阿里云Redis&MongoDB团队开源的用于校验2个redis数据是否一致的工具,通常用于redis数据迁移后正确性的校验。
24744 0
|
NoSQL Redis 监控
redis-shake数据同步&迁移&备份导入导出工具使用介绍
redis-shake是阿里云Redis&MongoDB团队开源的用于redis数据同步的工具。
71249 4
redis-shake数据同步&迁移&备份导入导出工具使用介绍
|
消息中间件 缓存 Java
面试官:你的项目有哪些难点?
面试官:你的项目有哪些难点?
574 2
|
JavaScript 前端开发 大数据
Vue.js 中的 `v-if` 和 `v-show`:理解与应用
Vue.js 中的 `v-if` 和 `v-show`:理解与应用
206 0
|
安全 Java
使用FilterChain实现Java中的过滤器链
使用FilterChain实现Java中的过滤器链
|
存储 缓存 NoSQL
这些年背过的面试题——Redis篇
本文是技术人面试系列Redis篇,面试中关于Redis都需要了解哪些基础?一文带你详细了解,欢迎收藏!
813 17
|
消息中间件 存储 Java
RocketMQ技术详解:从基础知识到内部设计原理
RocketMQ技术详解:从基础知识到内部设计原理
380 2
|
Java 开发者 Spring
springboot @Primary的概念与使用
【4月更文挑战第26天】在 Spring Framework 中,@Primary 注解用于标记一个 bean 作为在多个同类型的 bean 候选中进行自动装配时的首选 bean。这个注解非常有用,在配置和自动装配复杂的 Spring 应用程序时尤其如此,特别是当有多个 bean 实现相同的接口或继承相同的类时
1180 3
|
存储 Java
【面试问题】接口和抽象类有什么区别?
【1月更文挑战第27天】【面试问题】接口和抽象类有什么区别?
|
API
什么是接口幂等
什么是接口幂等
321 0

热门文章

最新文章