Redis从入门到精通之底层数据结构跳表 SkipList

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
性能测试 PTS,5000VUM额度
简介: 跳表(Skip List)是一种基于链表的数据结构,用于快速地插入、删除和查找元素。跳表通过多层级的指针数组来实现快速的操作,时间复杂度为O(log n),其中n为跳表中元素的个数。Redis中的有序集合(Sorted Set)就是通过跳表来实现的。

跳表(Skip List)是一种基于链表的数据结构,用于快速地插入、删除和查找元素。跳表通过多层级的指针数组来实现快速的操作,时间复杂度为O(log n),其中n为跳表中元素的个数。Redis中的有序集合(Sorted Set)就是通过跳表来实现的。

1. 跳表的底层原理

1.1 跳表的结构

跳表中的每个节点包含一个键值对,其中键用于排序元素,值用于存储具体的数据。跳表的每个节点都有多个指针,用于指向下一个节点。多个指针可以穿过一些节点,形成多层级的结构,从而实现快速的操作。

下图展示了一个包含7个元素的跳表:

level 2:    +---------+---------+---------+---------+
            |         |         |         |         |
            |   21:d  |   33:c  |   50:a  |   75:b  |
            |         |         |         |         |
level 1:    +---------+---------+---------+---------+
            |         |               |             |
            |   21:d  |              50:a           |
            |         |               |             |
level 0:    +---------+---------+---------+---------+
            |         |         |         |         |
            |   1:b   |   3:c   |   4:a   |   8:d   |
            |         |         |         |         |

跳表的每一层都是一个有序链表,其中第一层是完整的链表,包含所有元素,而其他层则包含部分元素。跳表中每个节点都包含一个键和一个值,键用于排序元素,值用于存储具体的数据。

跳表的指针数组中的指针数量是随机的,但通常是2或3个。指针数组的数量越多,节点的层数就越高,跳表的效率就越高。

跳表内存布局

image.png

1.2 跳表的操作

跳表的插入、删除和查找操作的时间复杂度都是O(log n),其中n为跳表中元素的个数。以下是跳表的常用操作:

  1. 插入操作:从头节点开始,沿着每一层的指针数组,找到待插入元素的位置,然后将元素插入到指定位置,并更新每一层的指针数组。

  2. 删除操作:从头节点开始,沿着每一层的指针数组,找到待删除元素的位置,然后将元素从指定位置删除,并更新每一层的指针数组。

  3. 查找操作:从头节点开始,沿着每一层的指针数组,找到指定元素的位置,并返回其值。

  4. 范围查找操作:从头节点开始,沿着每一层的指针数组,找到指定范围内的元素,并返回其值。

1.3 跳表的实现

跳表的实现可以分为以下几个步骤:

  1. 初始化:创建一个空的跳表,并设置头节点和尾节点。

  2. 插入操作:从头节点开始,沿着每一层的指针数组,找到待插入元素的位置,然后将元素插入到指定位置,并更新每一层的指针数组。

  3. 删除操作:从头节点开始,沿着每一层的指针数组,找到待删除元素的位置,然后将元素从指定位置删除,并更新每一层的指针数组。

  4. 查找操作:从头节点开始,沿着每一层的指针数组,找到指定元素的位置,并返回其值。

  5. 范围查找操作:从头节点开始,沿着每一层的指针数组,找到指定范围内的元素,并返回其值。

在Redis中,有序集合(Sorted Set)就是通过跳表来实现的。Redis使用跳表作为有序集合的底层数据结构,通过使用跳表,可以实现快速的插入、删除和查找操作,时间复杂度都是O(log n),其中n为有序集合中元素的个数。具体来说,Redis使用跳表来实现有序集合的存储,同时使用哈希表来实现成员和分值之间的映射关系,这样可以快速地通过成员查找到对应的分值,从而实现有序集合的排序和范围查找等操作。

跳表的核心设计要点

1.4 zskiplist的核心设计要点为:

  1. 头结点不持有任何数据, 且其level[]的长度为32
  2. 每个结点, 除了持有数据的ele字段, 还有一个字段score, 其标示着结点的得分, 结点之间凭借得分来判断先后顺序, 跳跃表中的结点按结点的得分升序排列.
  3. 每个结点持有一个backward指针, 这是原版跳跃表中所没有的. 该指针指向结点的前一个紧邻结点.
  4. 每个结点中最多持有32个zskiplistLevel结构. 实际数量在结点创建时, 按幂次定律随机生成(不超过32). 每个zskiplistLevel中有两个字段.
  5. forward字段指向比自己得分高的某个结点(不一定是紧邻的), 并且, 若当前zskiplistLevel实例在level[]中的索引为X, 则其forward字段指向的结点, 其level[]字段的容量至少是X+1. 这也是上图中, 为什么forward指针总是画的水平的原因.
  6. span字段代表forward字段指向的结点, 距离当前结点的距离. 紧邻的两个结点之间的距离定义为1.
  7. zskiplist中持有字段level, 用以记录所有结点(除过头结点外), level[]数组最长的长度.

总结

总之,跳表是一种基于链表的数据结构,用于快速地插入、删除和查找元素。跳表通过多层级的指针数组来实现快速的操作,时间复杂度为O(log n),其中n为跳表中元素的个数。在Redis中,有序集合就是通过跳表来实现的,通过使用跳表,可以实现快速的插入、删除和查找操作,时间复杂度都是O(log n),从而实现有序集合的存储和操作。

附录

如果要详细了解可参考
Redis为什么用跳表而不用平衡树
底层数据结构, 与Redis Value Type之间的关系

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
存储 消息中间件 缓存
Redis 5 种基础数据结构?
Redis的五种基础数据结构——字符串、哈希、列表、集合和有序集合——提供了丰富的功能来满足各种应用需求。理解并灵活运用这些数据结构,可以极大地提高应用程序的性能和可扩展性。
40 2
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
47 5
|
2月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
3月前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
64 8
|
3月前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
3月前
|
消息中间件 存储 缓存
redis支持的数据结构
redis支持的数据结构
43 2
|
2月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
2月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
265 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
42 1