redis 系列——5、跳跃表
概述
上篇博客我简单介绍了 redis 字典的实现原理,本篇博客我打算整理 redis 跳跃表实现原理。关于抽象概念跳跃表相关知识可以 点击这里 查看我之前的博客。
跳跃表
跳跃表有一种 有序 数据结构,它通过在每个节点中维护多个指向其他节点的指针达到快速访问的目的。
跳跃表查询的平均复杂度为 O(log n),最坏复杂度为 O(n)。在大多数情况下,它的效率和平衡树差不多,但技术难度上跳跃表相比平衡树简单许多。目前绝大多数程序使用跳跃表代替平衡树。
redis 使用跳跃表作为有序集合键(zSet)的实现方式之一:如果一个有序集合包含的元素数量比较多,或者说集合元素都是比较长的字符串时,redis 使用跳跃表作为有序集合键的实现原理。
除了有序集合键外,redis 还在集群节点中使用跳跃表作为内部数据结构,其中 redis 仅仅在这两块内容用到跳跃表。
zSkipListNode
redis 跳跃表是由头文件 redis.h 中的 zSkipListNode 和 zSkipList 两结构实现的:
- zSkipList:记录跳跃表本身,通过它快速访问和跳跃表相关的常用属性
- zSkipListNode:跳跃表中节点元素,通过它记录所有节点值
下面我们首先来看 zSkipListNode 的结构:
typedef struct zskiplistNode {// 层struct zskiplistLevel {// 前进指针struct zskiplistNode *forward;// 跨度unsigned int span;} level[];// 后退指针struct zskiplistNode *backward;// 分值double score;// 成员对象robj *obj;
} zskiplistNode;
- level:跳跃表的层级,其中每个数组元素包含两个属性:指针 和 跨度
- backward:跳跃表本身基于链表,通过该属性获取前一个 zskiplistNode 结构节点
- score:跳跃表必须包含可以判断大小的字段,通过该字段进行排序
- obj:记录元素属性值
level
每个跳跃表节点 level 数组的长度是随机的,redis 跳跃表默认最大长度为32。一般情况下,数组的长度越大,跳跃表的查询效率越高,关于其中原理可以参考概述中引用的博客内容。
level 数组元素的 forward 属性是指向 zSkipListNode 结构的指针,通过它指向后面的跳跃表节点,需要注意的一点是:forwared 属性指向包含该层级的第一个后续节点。
下面我举个简单的例子,假设现在存在5个跳跃表节点,它们随机出的 level 数组长度分别为 5、3、1、4,5。下面我通过简单图片描述该关系:
上图是一个抽象视图,我主要想表达 level 数组指针指向包含当前层级的下一个节点。
假设此时我们需要查询元素C:从元素A开始,根据最高层数组指针,直接判断元素E,发现元素E不是所求后,回到元素A。根据次高层指针,判断元素D不为所求后,回到元素A…依次一层一层向下遍历。这里我省略了根据 score 属性判断的过程,主要想说明:跳跃表遍历总是从 最高层数组 指针所形成的链表开始,依次向下遍历。
如果说 forwared 属性是指向包含当前层的下一个节点,那么 span 属性就是用来记录当前节点和被指向节点的距离。
就拿上图来说,我简单列举出几个元素数组元素的 span 值:
假设下标从1开始,和上图方块相对应。
A元素:
level[5].span = 4
level[4].span = 3
level[3].span = 1B元素:
level[3].span = 2
level[2].span = 2
level[1].span = 1
...
span 属性值越大,说明两个节点越远。它的值可以通过下层数组元素的 span 属性加合得到,而 level[1].span 除尾节点外,总是等于1。
遍历链表时,只需要 forwared 属性即可,span 属性主要用来计算节点间距离。
backward
backward 属性表示后退指针,通过该属性获取上一个节点。需要注意的一点是:除头节点外,其他节点总是指向 level 数组中 span 属性为1的节点,也就是最底层链表的前驱节点。具体我们看示例:
通过 backward 属性,redis 将链表改造为双向链表,方便从尾部向前遍历。
score 和 obj
在前面关于跳跃表的博客中,我们提到跳跃表必须包含可以用来排序的属性。否则,跳跃就失去了意义,使用跳跃表不会带来任何效率提升。
redis 使用 double 类型的 score 属性作为节点元素排序的基础,score 属性较小的元素排在链表前面,score 属性较大的元素排在后面。
redis 使用 obj 保存节点元素属性,除了用来排序的属性外,其他属性都可以在 obj 中进行记录。
下面我通过简单抽象示图描述其关系:
总结一下:obj 主要记录属性,score 主要用来排序。查找元素时根据 level 数组指针遍历,遍历过程中通过 score 属性判断向前遍历,还是退回到原节点,通过数组下一层组成的链表遍历。
zSkipList
zSkipList 的结构如下图所示:
typedef struct zskiplist {// 表头节点和表尾节点structz skiplistNode *header, *tail;// 表中节点的数量unsigned long length;// 表中层数最大的节点的层数int level;
} zskiplist;
- header:指向跳跃表头节点
- tail:执行跳跃表尾节点
- length:记录跳跃表的长度
- level:记录跳跃表中最高的节点层数
通过该结构可以快速获取跳跃表常用属性,让跳跃表长度,头尾节点以及最高层级的获取不再成为性能瓶颈。
redis 跳跃表示例
有了上面铺垫,我们来看 redis 跳跃表具体实例图:
这里我列举出 redis 跳跃表和常见跳跃表的不同:
- redis 跳跃表包含前缀指针,底层链表是双向的
- redis 跳跃表的头指针不保存数据,仅仅用来开头
- redis 跳跃表包含额外结构记录跳跃表长度、最高层级以及头尾节点指针
- redis 跳跃表可以包含相同 score 的元素,但 obj 属性必须唯一
- redis 跳跃表在 score 属性相同时,根据 obj 大小判断。根据不同的场景,实现不同的 obj 大小比较方法
个人理解 redis 跳跃表设置为双向链表主要为了方便查询范围数据:如获取 score 小于某个值的节点时,找到小于它的最大节点,根据 score 属性依次向前遍历即可。除此之外,可以方便获取值 从大到小 排序的链表。
相比传统跳跃表,redis 跳跃表采用以空间换时间的方式提升部分操作的效率。需要特别注意的一点是:redis 跳跃表的头指针指向 level 长度为32、其它属性为空的跳跃表节点。
跳跃表常用API
下面我通过图的形式,列举出 redis 跳跃表常见api:
参考:
《redis设计与实现》黄健宏著
redis 系列——5、跳跃表相关推荐
- Redis面试题系列:跳跃表
简介 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.在大部分情况下,跳跃表的效率可以和平衡树相媲美,而且实现比平衡树更加简单. ...
- Redis内部数据结构-跳跃表
今天学习了跳跃表,记录一下下~ 一.跳跃表简介 跳跃表是一种随机化数据结构,基于并联的链表,其效率可以比拟平衡二叉树,查找.删除.插入等操作都可以在对数期望时间内完成,对比平衡树,跳跃表的实现要简单直 ...
- redis为什么选择了跳跃表而不是红黑树
Redis只在两个地方用到了跳跃表,一个是实现有序集合键(zset),另一个是在集群节点中用作内部数据结构,除此之外,跳表在Redis里面没有其他用途. 但是为什么用跳表而不用红黑树呢?猜想如下: 1 ...
- redis(五)跳跃表
一:基本概念 跳跃表是一种随机化的数据结构,在查找.插入和删除这些字典操作上,其效率可比拟于平衡二叉树(如红黑树),大多数操作只需要O(log n)平均时间,但它的代码以及原理更简单.跳跃表的定义如下 ...
- Redis - 跳跃表
一.跳跃表简介 跳跃表(skiplist)是一种随机化的数据结构,由 William Pugh 在论文<Skip lists: a probabilistic alternative to ba ...
- Redis面试题-Redis跳跃表
本文参考 嗨客网 Redis面试题 Redis跳跃表 什么是跳跃表 Redis 中的跳跃表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的. 为什么使用跳 ...
- redis 系列7 数据结构之跳跃表
redis 系列7 数据结构之跳跃表 原文:redis 系列7 数据结构之跳跃表 一.概述 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问 ...
- Redis设计与实现之跳跃表
跳跃表简介 我们先抛开redis,单独了解下跳越表 skiplist本质上也是一种查找结构,用于解决算法中的查找问题(Searching),即根据给定的key,快速查到它所在的位置(或者对应的valu ...
- 学习笔记-Redis设计与实现-跳跃表
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的. 跳跃表支持平均O(logN).最坏O(N)复杂度的节点查找,还可以通过顺序性操 ...
- Redis之跳跃表(面试重点容易考)
跳跃表 1. 跳跃表的用处 2. 跳跃表的具体示例 跳跃表的查找 跳跃表的具体实现 本文重点 1. 跳跃表的用处 有序集合(zset)的底层可以采用数组, 链表, 平衡树等结果来实现, 但是他们都有各 ...
最新文章
- JAVA swing初级教程(四)
- 语义SLAM开源代码汇总
- LeakDiag 微软一款检测memory leak的工具
- 美团O2O排序解决方案——线下篇
- Bug访问豆瓣403forbidden
- Spring MVC 配置--解剖
- ansible 学习笔记
- js Date 函数方法
- win10 设置游戏全屏
- postgresql cast转换类型
- PEP8——Python代码规范
- macOS Monterey更新后遇到的bug问题汇总与解决
- tensorflow中prefetch最合适的用法
- 跟着百度学PHP[3]-PHP中结构嵌套之循环结构与条件结构嵌套
- java上传图片裁剪_浅谈java图片上传之剪切
- JavaWeb笔记(一)Java网络编程
- 关于jxls2.6.0的学习以及遇到的问题(八)
- android shell卸载应用程序,adb shell删除系统apk
- 简析发送手机验证码原理
- 两个正态总体方差比的置信区间