跳跃表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

redis 使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,redis 就会使用跳跃表来作为有序集合键的底层实现。

redis 只在两个地方使用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。

1   跳跃表的实现

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

上面最左边是一个  zskiplist 结构。该结构包含以下属性:

1、header:指向跳跃表的表头节点。

2、tail:指向跳跃表的表尾节点。

3、level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。

4、length:记录跳跃表的长度,也就是跳跃表目前包含节点的数量(表头节点的层数不计算在内)。

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

1、层(level):节点中用 L1、L2 等字样标记节点的各个层,L1 代表第一层,L2 代表第二层,以此类推。每个层都带有两个属性:前进指针和跨度。

前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。在上面图片中,连线上带有数字的箭头就代表前进指针,而那个数字则代表跨度。

2、后退(backward)指针:节点中用 BW 字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。

3、分值(score):各个节点中的 1.0、2.0 是节点所保存的分值。在跳跃表中,节点按各自保存的分值从小到大排列。

4、成员对象(obj):各个节点的 o1、o2 是节点保存的成员对象。

注意:

表头节点跟其他节点的构造是一样的,只不过表头节点的这些属性不会用到,因此省略了这部分。

1.1    跳跃表节点

跳跃表节点的实现由 redis.h/ 在 skiplistNode 结构定义:

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

1、层

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

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

2、前进指针

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

下面展示了带有不同层高的节点:

下面用虚线表示出了程序从表头向表尾方向,遍历跳跃表中所有节点的路径:

1、迭代程序首先访问跳跃表的第一个节点(表头),然后从第四层的前进指针移动到表中的第二个节点。

2、在第二个节点时,程序沿着第二层的前进指针移动到表中的第三个节点。

3、 在第三个节点时,程序同样沿着第二层的前进指针移动到表中的第四个节点。

4、当程序再次沿着第四个节点的前进指针移动时,它碰到一个 NULL,程序知道这时已经到达了跳跃表的表尾,于是结束这次遍历。

3、跨度

层的跨度( level[i].span 属性)用于记录两个节点之间的距离:

1、两个节点之间的跨度越大,它们相距得就越远。

2、指向 NULL 的所有前进指针的跨度都为0,因为它们没有连向任何节点。

跨度实际上是用来计算排位(rank)的。在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是目标节点在跳跃表中的排位。

举个例子,下图用虚线标记了在跳跃表中查找分值为 2.0、成员对象为 o2 的节点时,沿途经历的层:在查找节点的过程中,程序经过了两个跨度为1的节点,因此可以计算出,目标节点在跳跃表中的排位为2。

4、后退指针

节点的后退指针(backward 属性)用于从表尾向表头方向访问节点。

跟可以一次跳过多个节点的前进指针不同,因为每个节点只有一个后退指针,所以每次只能后退至前一个节点。

下图用虚线展示了如何从表尾向表头遍历跳跃表中的所有节点:

程序首先通过跳跃表的tail指针访问表尾节点,然后通过后退指针访问倒数第二个节点,之后再沿着后退指针访问倒数第三个节点,再之后遇到指向 NULL 的后退指针,于是访问结束。

5、分值和成员

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

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

在同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值却可以是相同的:

分值相同的节点将按照成员对象在字典中的大小来进行排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向)。

在下图中所示的跳跃表中,三个跳跃表节点都保存了相同的分值10086.0。

但保存成员对象o1的节点却排在保存成员对象o2和o3的节点的前面,而保存成员对象o2的节点又排在保存成员对象o3的节点之前,由此可见,o1、o2、o3三个成员对象在字典中的排序为o1<=o2<=o3。

1.2    跳跃表

靠多个跳跃表节点就可以组成一个跳跃表,如下所示:

zskiplist 结构的定义如下:

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

通过使用一个 zskiplist 结构来持有这些节点,程序可以更方便地对整个跳跃表进行处理。比如快速访问跳跃表的表头节点和表尾节点,或者快速地获取跳跃表节点的数量(也即是跳跃表的长度)等信息,如下图所示:

这样获取表头、表尾节点,表长,以及表中最高层数的复杂度均为 O(1)。

2   跳跃表 API

3    回顾

  • 跳跃表是有序集合的底层实现之一, 除此之外它在 Redis 中没有其他应用。

  • Redis 的跳跃表实现由 zskiplist 和 zskiplistNode 两个结构组成, 其中 zskiplist 用于保存跳跃表信息(比如表头节点、表尾节点、长度), 而 zskiplistNode 则用于表示跳跃表节点。

  • 每个跳跃表节点的层高都是 1 至 32 之间的随机数。

  • 在同一个跳跃表中, 多个节点可以包含相同的分值, 但每个节点的成员对象必须是唯一的。

  • 跳跃表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。

参考资料

[1]  redis设计与实现

虚线 实现_redis跳跃表实现相关推荐

  1. arcengine遍历属性表_Redis源码解析四--跳跃表

    Redis 跳跃表(skiplist) 1. 跳跃表(skiplist)介绍 定义:跳跃表是一个有序链表,其中每个节点包含不定数量的链接,节点中的第i个链接构成的单向链表跳过含有少于i个链接的节点. ...

  2. 0046算法笔记——【随机化算法】舍伍德随机化思想解决跳跃表问题

    问题描述 如果用有序链表来表示一个含有n个元素的有序集S,则在最坏情况下,搜索S中一个元素需要O(n)计算时间.提高有序链表效率的一个技巧是在有序链表的部分结点处增设附加指针以提高其搜索性能.在增设附 ...

  3. Redis之跳跃表实现

    http://redisbook.com/index.html 跳跃表的实现 Redis 的跳跃表由 redis.h/zskiplistNode 和 redis.h/zskiplist 两个结构定义, ...

  4. 《Reids 设计与实现》第三章 跳跃表

    <Reids 设计与实现>第三章 跳跃表 文章目录 <Reids 设计与实现>第三章 跳跃表 一.跳跃表 1.简介 2.跳跃表的实现 3.跳跃表 API 4.重点回顾 一.跳跃 ...

  5. redis(五)跳跃表

    一:基本概念 跳跃表是一种随机化的数据结构,在查找.插入和删除这些字典操作上,其效率可比拟于平衡二叉树(如红黑树),大多数操作只需要O(log n)平均时间,但它的代码以及原理更简单.跳跃表的定义如下 ...

  6. Redis设计与实现之跳跃表

    跳跃表简介 我们先抛开redis,单独了解下跳越表 skiplist本质上也是一种查找结构,用于解决算法中的查找问题(Searching),即根据给定的key,快速查到它所在的位置(或者对应的valu ...

  7. 跳跃表原理及redis跳跃表的应用

    跳跃表的实现还是一个链表,是一个有序的链表,在遍历的时候基于比较,但普通链表只能遍历,跳跃表加入了一个层(也叫索引)的概念,层数越高的元素越少,每次先从高层查找,再逐渐降层,直到找到合适的位置.从图中 ...

  8. Redis数据结构-跳跃表

    跳跃表(skiplist)是一种有序数据结构, 它通过在每个节点中维持多个指向其他节点的指针, 从而达到快速访问节点的目的. 跳跃表支持平均 O(log N) 最坏 O(N) 复杂度的节点查找, 还可 ...

  9. Redis中跳跃表浅析

    一.前言 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.跳跃表支持平均O(logN).最坏O(N)复杂度的节点查找,还可以通过 ...

最新文章

  1. HDOJ2795 Billboard【线段树】
  2. Github 代码上边的Raw、Blame、History是啥意思?
  3. mysql技术分享-- 视图是什么
  4. android webview权限申请_android - 在运行时向摄像机请求WebView权限 - 堆栈内存溢出...
  5. python爬取新闻网站标题_python如何正确抓取网页标题
  6. Java8 新特性 Optional
  7. 吴恩达深度学习5.1笔记_Sequence Models_循环序列模型
  8. 大众eagit_试驾大众全新高尔夫GTI
  9. SheetForm.cs
  10. 牛客——数据库实战(1~30)
  11. 2.2.4 ES 6语法与ES 5语法
  12. 伽罗华域(Galois Field)上的四则运算
  13. 2017-11-20 白银解说
  14. 学习计算机英语总结,英语学习方法总结
  15. mvn找不到:log4j:log4j:1.2.17或者导入org.apache.log4j.Logger出现错误的原因和解决办法
  16. 如何维持手机电池寿命_延长iPhone 手机电池寿命的几个方法
  17. Sentinel-2(哨兵2号)SNAP预处理
  18. 利用记忆规律促进学生有效学习(记忆规律在教学中的运用)
  19. 创建微信小程序日期和时间的组件
  20. Linux上配置BIP语言编译器及引擎

热门文章

  1. 【活动(深圳)DevOps/.NET 微服务 秋季分享会】火热报名中!
  2. 记一次.net core 集成vue 实践
  3. Asp.Net Core实战
  4. Asp.Net Core SignalR 与微信小程序交互笔记
  5. Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)
  6. 发达国家与发展中国家编程语言技术的分布差异性
  7. Work Time Manager【开源项目】- 创建自己日志组件 2.0重构
  8. Android之在eclipse编译项目出现unable to resolve target android-20解决办法
  9. Android之获取应用程序(包)的大小-----PackageManager的使用(二)
  10. opencv 平面法向量_在OpenCV中绘制平面的法向量