Redis 跳表原理详解

一、引言

在 Redis 中,有序集合(Sorted Set)是一种非常重要的数据结构,它可以实现元素的有序存储和高效查找。而实现有序集合的底层数据结构之一就是跳表(Skip List)。跳表是一种随机化的数据结构,它通过在链表的基础上增加多层索引来提高查找效率。本文将详细介绍 Redis 跳表的原理,并结合图文进行说明。

二、链表的局限性

在介绍跳表之前,我们先来回顾一下普通链表的查找操作。假设我们有一个有序链表,要查找其中的某个元素,需要从链表的头节点开始,依次遍历每个节点,直到找到目标元素或者遍历到链表的末尾。这种查找方式的时间复杂度是 (O(n)),其中 n 是链表的长度。当链表长度很大时,查找效率会非常低。

下面是一个简单的有序链表示例:

+---+    +---+    +---+    +---+
| 1 | -> | 3 | -> | 5 | -> | 7 |
+---+    +---+    +---+    +---+

如果要查找元素 5,需要从头节点开始,依次比较 1、3,直到找到 5,总共需要比较 3 次。

三、跳表的基本思想

跳表的基本思想是在链表的基础上增加多层索引,通过这些索引可以快速跳过一些不必要的节点,从而减少查找的时间复杂度。具体来说,跳表会为每个节点随机分配一个层数,层数越高的节点越稀疏。在查找时,从最高层的索引开始,快速定位到一个大致的范围,然后逐渐向下层移动,直到找到目标节点或者确定目标节点不存在。

下面是一个简单的跳表示例:

Level 3: +---------------------+|                     |v                     v
Level 2: +---+         +---+    +---+| 1 | -------> | 5 | -> | 7 |+---+         +---+    +---+|             |        |v             v        v
Level 1: +---+    +---+ +---+    +---+| 1 | -> | 3 | | 5 | -> | 7 |+---+    +---+ +---+    +---+

在这个跳表中,有三层索引。当我们要查找元素 5 时,从最高层(Level 3)开始,由于最高层只有一个节点 1,它小于 5,所以继续在这一层向右查找,发现没有其他节点了,于是向下移动到 Level 2。在 Level 2 中,节点 1 小于 5,继续向右查找,找到节点 5,此时已经找到了目标元素,查找结束。总共只需要比较 2 次,比普通链表的查找效率更高。

四、跳表的结构

节点结构

Redis 跳表的节点结构包含以下几个部分:

  • 成员对象(member):即有序集合中的元素,是一个字符串对象。
  • 分值(score):用于对元素进行排序的数值,类型为双精度浮点数。
  • 后退指针(backward):指向前一个节点,用于从后向前遍历跳表。
  • 层(level):每个节点包含多个层,每层都有一个前进指针(forward)和一个跨度(span)。前进指针指向下一个节点,跨度表示从当前节点到下一个节点跨越的节点数量。

下面是一个简化的节点结构示意图:

+-----------------+
| member: "apple" |
| score: 3.5      |
| backward: prev  |
| level: [        |
|   { forward: n1, span: 2 }, |
|   { forward: n2, span: 3 }, |
|   ...                      |
| ]               |
+-----------------+

跳表结构

Redis 跳表由一个头节点和一个尾节点组成,头节点的层数通常是最大层数,尾节点的分值为正无穷大。跳表还记录了节点的数量和最大层数。

下面是一个简化的跳表结构示意图:

+-----------------+
| header:         |
|   level: [      |
|     { forward: n1, span: 1 }, |
|     { forward: n2, span: 2 }, |
|     ...                      |
|   ]               |
| length: 5        |
| level: 3         |
+-----------------+

五、跳表的操作

查找操作

查找操作是跳表的核心操作之一。具体步骤如下:

  1. 从最高层的链表开始,沿着当前层的链表向前比较节点的分值。如果当前节点的下一个节点的分值小于目标分值,则继续在当前层向前移动;如果下一个节点的分值大于目标分值,则向下移动一层。
  2. 重复步骤 1,直到找到目标节点或者到达底层链表且遍历完所有可能的节点。

下面是一个查找元素的示例:

Level 3: +---------------------+|                     |v                     v
Level 2: +---+         +---+    +---+| 1 | -------> | 5 | -> | 7 |+---+         +---+    +---+|             |        |v             v        v
Level 1: +---+    +---+ +---+    +---+| 1 | -> | 3 | | 5 | -> | 7 |+---+    +---+ +---+    +---+查找元素 5:
1. 从 Level 3 开始,头节点 1 小于 5,继续向右查找,没有其他节点,向下移动到 Level 2。
2. 在 Level 2 中,节点 1 小于 5,继续向右查找,找到节点 5,查找结束。

插入操作

插入操作的步骤如下:

  1. 按照查找操作的方式,找到要插入的位置。
  2. 在底层链表中插入新节点。
  3. 根据一定的概率决定是否将新节点提升到更高层。通常,一个节点有 1/2 的概率出现在第 1 层,有 1/4 的概率出现在第 2 层,有 1/8 的概率出现在第 3 层,依此类推。

下面是一个插入元素的示例:

原始跳表:
Level 2: +---+         +---+    +---+| 1 | -------> | 5 | -> | 7 |+---+         +---+    +---+|             |        |v             v        v
Level 1: +---+    +---+ +---+    +---+| 1 | -> | 3 | | 5 | -> | 7 |+---+    +---+ +---+    +---+插入元素 4:
1. 按照查找操作,找到插入位置在 3 和 5 之间。
2. 在底层链表中插入节点 4。
3. 假设节点 4 有 1/2 的概率提升到第 2 层,这里假设提升成功。插入后的跳表:
Level 2: +---+    +---+    +---+    +---+| 1 | -> | 4 | -> | 5 | -> | 7 |+---+    +---+    +---+    +---+|        |        |        |v        v        v        v
Level 1: +---+    +---+    +---+    +---+    +---+| 1 | -> | 3 | -> | 4 | -> | 5 | -> | 7 |+---+    +---+    +---+    +---+    +---+

删除操作

删除操作的步骤如下:

  1. 按照查找操作的方式,找到要删除的节点。
  2. 将该节点从每一层链表中移除,并更新相应的指针。

下面是一个删除元素的示例:

原始跳表:
Level 2: +---+    +---+    +---+    +---+| 1 | -> | 4 | -> | 5 | -> | 7 |+---+    +---+    +---+    +---+|        |        |        |v        v        v        v
Level 1: +---+    +---+    +---+    +---+    +---+| 1 | -> | 3 | -> | 4 | -> | 5 | -> | 7 |+---+    +---+    +---+    +---+    +---+删除元素 4:
1. 按照查找操作,找到节点 4。
2. 将节点 4 从每一层链表中移除,并更新指针。删除后的跳表:
Level 2: +---+         +---+    +---+| 1 | -------> | 5 | -> | 7 |+---+         +---+    +---+|             |        |v             v        v
Level 1: +---+    +---+ +---+    +---+| 1 | -> | 3 | | 5 | -> | 7 |+---+    +---+ +---+    +---+

六、跳表的复杂度分析

时间复杂度

跳表的查找、插入和删除操作的平均时间复杂度都是 (O(log n)),其中 n 是跳表中节点的数量。这是因为跳表通过多层索引的方式,每次可以跳过一部分节点,使得查找过程类似于二分查找,从而将时间复杂度从普通链表的(O(n)) 降低到了 (O(log n))。

空间复杂度

跳表的空间复杂度是 (O(n)),因为每个节点除了存储本身的数据外,还需要额外的指针来维护多层索引。不过,由于每个节点的层数是随机分配的,平均情况下每个节点的层数是常数级别的,所以空间复杂度仍然是线性的。

七、场景应用

跳表(Skip List)因其高效的动态操作(插入、删除、查询均为 O (log n) 平均时间复杂度)和实现简单性,被广泛应用于以下场景:

1. 数据库索引

  • LevelDB/RocksDB
    Google 开发的高性能键值存储引擎 LevelDB 及其衍生项目 RocksDB,均使用跳表(称为 SkipList)作为内存索引结构(MemTable)。跳表的有序性和高效插入删除能力,使其适合管理高频更新的内存数据。

  • HBase
    HBase 的内存存储组件 MemStore 底层也依赖跳表实现,用于快速查询和维护数据。

2. 分布式系统

  • Apache Kafka
    Kafka 的日志分段索引(LogSegment)使用跳表来维护偏移量(Offset)到物理位置的映射,确保消息的有序性和快速检索。

  • Consistent Hashing
    跳表可用于分布式哈希表(DHT)的一致性哈希环管理,支持节点动态加入 / 退出时的高效调整。

3. 网络设备

  • 路由表管理
    网络设备(如路由器)的路由表需高效匹配目的 IP,跳表的多层索引结构可加速最长前缀匹配(Longest Prefix Match)。

  • Nginx 负载均衡
    Nginx 的一致性哈希负载均衡算法中,跳表被用于维护虚拟节点的有序分布。

4. 编程语言与框架

  • Java ConcurrentSkipListMap
    Java 并发包中的 ConcurrentSkipListMap 基于跳表实现,提供线程安全的有序键值存储,适合高并发场景。

  • Python 的 sortedcontainers 库
    第三方库 sortedcontainers 中的 SortedList 基于跳表,支持高效的插入、删除和排序操作。

5. 文件系统与存储

  • 元数据管理
    分布式文件系统(如 Ceph)的元数据服务器(MDS)可能使用跳表管理目录结构或文件属性的有序索引。

  • 日志结构化存储
    日志系统(如 ELK Stack)的索引层可能借助跳表优化日志查询性能。

6. 其他场景

  • 游戏开发
    用于管理游戏对象的空间索引(如二维网格中的动态碰撞检测)。

  • 区块链
    某些区块链项目(如 Hyperledger Fabric)的账本索引可能采用跳表优化交易查询。

跳表的优势与适用场景总结

场景跳表优势
动态数据管理插入、删除高效,无需频繁调整树结构(对比红黑树)。
并发场景实现简单,锁粒度小(如无锁跳表),适合高并发环境。
有序数据结构需求天然支持有序遍历,无需额外排序操作。
内存敏感场景相比平衡树,跳表的节点结构更简单,内存占用较低。

对比:跳表 vs 平衡二叉树

特性跳表平衡二叉树(如红黑树)
时间复杂度平均 O (log n),最坏 O (n)平均 / 最坏均为 O (log n)
实现难度简单,无需复杂的旋转操作复杂,需维护平衡因子
并发支持容易实现无锁或细粒度锁锁粒度较大,并发性能受限
适用场景动态数据、高并发、内存敏感静态数据、低并发、需要严格 O (log n) 保证

七、总结

Redis 跳表是一种非常高效的数据结构,它通过在链表的基础上增加多层索引,实现了元素的有序存储和高效查找。跳表的查找、插入和删除操作的平均时间复杂度都是 (O(log n)),空间复杂度是 (O(n))。在实际应用中,跳表凭借其高效性和简单性,成为许多高性能系统的底层选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/74012.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

识别并脱敏上传到deepseek/chatgpt的文本文件中的身份证/手机号

本文将介绍一种简单高效的方法解决用户在上传文件到DeepSeek、ChatGPT,文心一言,AI等大语言模型平台过程中的身份证号以及手机号等敏感数据识别和脱敏问题。 DeepSeek、ChatGPT,Qwen,Claude等AI平台工具快速的被接受和使用,用户每天上传的文本数据中潜藏着大量敏感信息,…

Spring 如何创建 Bean 实例的?

Spring 创建 Bean 实例的过程主要由 BeanFactory 接口及其实现类(通常是 AbstractBeanFactory 的 doGetBean 方法和 DefaultListableBeanFactory 的 preInstantiateSingletons 方法)负责。这个过程涉及多个步骤,包括 Bean 定义的解析、依赖的…

第六:go 操作 redis-go

Redis 在项目开发中redis的使用也比较频繁,本文介绍了Go语言中go-redis库的基本使用。 Redis介绍 Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外&am…

【RabbitMQ】RabbitMQ如何保证消息不丢失?

为了保证消息不丢失,需要在生产者、RabbitMQ本身和消费者三个环节采取相应措施。 1.生产者端:确保消息发送成功 1.1开启消息确认机制(Publisher Confirms) 原理: 生产者发送消息后,RabbitMQ会返回一个确认(ACK),表示消息已成功…

fastapi+angular外卖系统

说明: fastapiangular外卖系统 1.美食分类(粥,粉,面,炸鸡,炒菜,西餐,奶茶等等) 2.商家列表 (kfc,兰州拉面,湘菜馆,早餐店…

Kafka-Exporter 9308端口启用TLS认证的完整指南

#作者:张桐瑞 文章目录 1 方案描述2 涉及版本3 使用CA自签证书3.1一键生成证书脚本3.1.1证书脚本3.1.2执行结果 3.2分步自建证书过程3.2.1生成CA私钥3.2.2生成CA自签名证书3.2.3生成服务器私钥和证书申请文件CRS 3.3最终的文件列表 4 Exporter启动命令4.1参数说明 …

NFS共享搭建

准备工作 首先确保已经建了两台虚拟机,都是桥接模式,一台是windows server 2019 一台是centos7 用户配额教程,是在windows server 2019中,先新建虚拟池,然后创建虚拟磁盘,记得添加磁盘类型要选择第三个,要不…

DFT mode下hard phy STA Nopath

hard Phy boundary No Path 1. shift mode; shift cornor出现No Path的; PHY SI SO在shift mode必须有timing的path; 展示为No constrained path; check step: report_timing -though NO constrained path set timing_report_unconstrained true report again you will…

【工作记录】F12查看接口信息及postman中使用

可参考 详细教程:如何从前端查看调用接口、传参及返回结果(附带图片案例)_f12查看接口及参数-CSDN博客 1、接口信息 接口基础知识2:http通信的组成_接口请求信息包括-CSDN博客 HTTP类型接口之请求&响应详解 - 三叔测试笔记…

《自然》:陆地蒸散量研究的统计失误被撤回-空间加权平均的计算方法

文章目录 前言一、空间加权平均的计算方法二、代码1.Python 实现2.MATLAB代码 前言 In this article, we calculated global land evapotranspiration for 2003 to 2019 using a mass-balance approach. To do this, we calculated evapotranspiration as the residual of the…

开源软件许可证冲突的原因和解决方法

1、什么是开源许可证以及许可证冲突产生的问题 开源软件许可证是一种法律文件,它规定了软件用户、分发者和修改者使用、复制、修改和分发开源软件的权利和义务。开源许可证是由软件的版权所有者(通常是开发者或开发团队)发布的,它…

【el-upload】el-upload组件 - list-type=“picture“ 时,文件预览展示优化

目录 问题图el-upload预览组件 PicturePreview效果展示 问题图 el-upload <el-uploadref"upload"multipledragaction"#":auto-upload"false":file-list"fileList"name"files":accept".png,.jpg,.jpeg,.JGP,.JPEG,.…

微前端 qiankun vite vue3

文章目录 简介主应用 qiankun-main vue3 vite子应用 qiankun-app-vue2 webpack5子应用 qiankun-react webpack5子应用 quankun-vue3 vite遇到的问题 简介 主要介绍以qiankun框架为基础&#xff0c;vite 搭建vue3 项目为主应用&#xff0c;wepack vue2 和 webpack react 搭建的…

C#从入门到精通(1)

目录 第一章 C#与VS介绍 第二章 第一个C#程序 &#xff08;1&#xff09;C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 &#xff08;2&#xff09;程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…

东隆科技携手PRIMES成立中国校准实验室,开启激光诊断高精度新时代

3月12日&#xff0c;上海慕尼黑光博会期间&#xff0c;东隆科技正式宣布与德国PRIMES共同成立“中国校准实验室”。这一重要合作标志着东隆科技在本地化服务领域的优势与PRIMES在激光光束诊断领域的顶尖技术深度融合&#xff0c;旨在为中国客户提供更快速、更高精度的服务以及本…

HarmonyOS Next~鸿蒙系统架构设计解析:分层、模块化与智慧分发的技术革新

HarmonyOS Next&#xff5e;鸿蒙系统架构设计解析&#xff1a;分层、模块化与智慧分发的技术革新 ​ ​ 鸿蒙操作系统&#xff08;HarmonyOS&#xff09;作为华为自主研发的分布式操作系统&#xff0c;其架构设计以全场景、多设备协同为核心目标&#xff0c;通过分层架构、模…

Vue Router工作原理探究

摘要&#xff1a; 随着单页应用&#xff08;SPA&#xff09;的广泛流行&#xff0c;路由系统成为前端开发中至关重要的部分。Vue Router作为Vue.js官方的路由管理器&#xff0c;为Vue应用提供了强大的路由功能。本文深入探讨Vue Router的工作原理&#xff0c;包括其核心概念、路…

SysOM 可观测体系建设(一):万字长文解读低开销、高精度性能剖析工具livetrace

可观测性是一种通过分析系统输出结果并推断和衡量系统内部状态的能力。谈及可观测性一般包含几大功能&#xff1a;监控指标、链路追踪、告警日志&#xff0c;及 Continues Profiling 持续剖析能力。对于操作系统可观测&#xff0c;监控指标可以帮助查看各个子系统&#xff08;I…

网络安全设备配置与管理-实验4-防火墙AAA服务配置

实验4-p118防火墙AAA服务配置 从这个实验开始&#xff0c;每一个实验都是长篇大论&#x1f613; 不过有好兄弟会替我出手 注意&#xff1a;1. gns3.exe必须以管理员身份打开&#xff0c;否则ping不通虚拟机。 win10虚拟机无法做本次实验&#xff0c;必须用学校给的虚拟机。首…

路由Vue Router基本用法

路由的作用是根据URL来匹配对应的组件&#xff0c;并且无刷新切换模板的内容。vue.js中&#xff0c;可使用Vue Router来管理路由&#xff0c;让构建单页应用更加简单。 一、效果 二、实现 1.项目中安装Vue Router插件 pnpm install vue-routerlastest 2.main.js import { …