Redis底层数据结构之深入理解跳表(1)

article/2025/6/13 0:43:45

        在上一篇文章中我们详细的介绍了一下Redis中跳表的结构以及为什么Redis要引入跳表而不是平衡树或红黑树。这篇文章我们就来详细梳理一下跳表的增加、搜索和删除步骤。

SkipList的初始化

        跳表初始化时,将每一层链表的头尾节点创建出来并使用集合将头尾节点进行存储,头尾节点的层数随机指定,且头尾节点的层数就代表当前跳表的层数。初始化之后,跳表的结构如下所示:

        跳表初始化的相关代码如下所示:

public class SkiplistNode {public int data;public SkiplistNode next;public SkiplistNode down;public int level;public SkiplistNode(int data, int level) {this.data = data;this.level = level;}
}public LinkedList<SkiplistNode> headNodes;
public LinkedList<SkiplistNode> tailNodes;public int curLevel;public Random random;public Skiplist() {random = new Random();headNodes = new LinkedList<>();//headNodes用于存储每一层的头节点tailNodes = new LinkedList<>();//tailNodes用于存储每一层的尾节点curLevel = getRandomLevel();//初始化跳表时,跳表的层数随机指定//指定了跳表的初始的随机层数后,就需要将每一层的头节点和尾节点创建出来并构建好关系SkiplistNode head = new SkiplistNode(Integer.MIN_VALUE, 0);SkiplistNode tail = new SkiplistNode(Integer.MAX_VALUE, 0);for (int i = 0; i <= curLevel; i++) {head.next = tail;headNodes.addFirst(head);tailNodes.addFirst(tail);SkiplistNode headNew = new SkiplistNode(Integer.MIN_VALUE, head.level + 1);SkiplistNode tailNew = new SkiplistNode(Integer.MAX_VALUE, tail.level + 1);headNew.down = head;tailNew.down = tail;head = headNew;tail = tailNew;}
}

SkipList的添加方法

        每一个元素添加到跳表中时,首先需要随机指定这个元素在跳表中的层数,如果随机指定的层数大于了原有跳表的层数,那么在将新元素加入到跳表之前,还要对跳表的层数进行扩大,而跳表层数的扩大就是将头尾节点的层数进行扩大。下面给出需要扩大跳表层数的依次添加过程。

        初始状态,跳表的层数为2,如下图所示:

        现在要向跳表中添加元素80,并随机指定了其层数为3,大于了当前跳表的层数2,此时需要先将跳表的层数扩充到3,如下图所示:

        最后将元素80插入到跳表中,插入时从顶层向下逐层插入,如下图所示:

        跳表的添加方法的相关代码如下所示:

public void add(int num) {int level = getRandomLevel();//获取本次添加的值的层数//如果本次添加的值的层数大于当前跳表的层数,则需要在添加当前值前先将跳表层数扩充if (level > curLevel) {expanLevel(level - curLevel);}//curNode表示num值在当前层对应的节点SkiplistNode curNode = new SkiplistNode(num, level);//preNode表示curNode在当前层的前一个节点SkiplistNode preNode = headNodes.get(curLevel - level);for (int i = 0; i <= level; i++) {//从当前层的head节点开始向后遍历,直到找到一个preNode//使得preNode.data < num <= preNode.next.datawhile (preNode.next.data < num) {preNode = preNode.next;}//将curNode插入到preNode和preNode.next中间curNode.next = preNode.next;preNode.next = curNode;//如果当前并不是0层,则继续向下层添加节点if (curNode.level > 0) {SkiplistNode downNode = new SkiplistNode(num, curNode.level - 1);curNode.down = downNode;//curNode指向下一层的节点curNode = downNode;//curNode向下移动一层}//preNode向下移动一层preNode = preNode.down;}
}private void expanLevel(int expanCount) {SkiplistNode head = headNodes.getFirst();//取出头节点中level最高的SkiplistNode tail = tailNodes.getFirst();//取出尾节点中level最高的//循环扩充level,直到与新加入节点的level相等for (int i = 0; i < expanCount; i++) {SkiplistNode headNew = new SkiplistNode(Integer.MIN_VALUE, head.level + 1);SkiplistNode tailNew = new SkiplistNode(Integer.MAX_VALUE, tail.level + 1);headNew.down = head;tailNew.down = tail;head = headNew;tail = tailNew;headNodes.addFirst(head);tailNodes.addFirst(tail);}
}

SkipList的搜索方法

        在跳表中搜索一个元素时,需要从顶层开始,逐层向下搜索。当目标值大于当前节点的后一个节点值时,需要在本层链表上继续向后搜索;当目标值大于当前节点值,小于当前节点的后一个节点值时,向下移动一层,并从下层链表的同节点位置向后搜索;当目标值等于当前节点值时,搜索结束并返回。具体流程如下图所示:

        跳表的搜索代码如下所示:

public boolean search(int target) {SkiplistNode curNode = headNodes.getFirst();//从顶层开始寻找,curNode表示当前遍历到的节点while (curNode != null) {if (curNode.next.data == target) {return true;//从顶层开始寻找,curNode表示当前遍历到的节点} else if (curNode.next.data > target) {//curNode的后一节点值大于target,说明目标节点在curNode和curNode.next之间//此时需要向下层寻找curNode = curNode.down;} else {//curNode的后一节点值小于target,说明目标节点在curNode的后一节点的后面//此时在本层继续向后寻找curNode = curNode.next;}}return false;
}

SkipList的删除方法

        当在跳表中需要删除一个元素时,则需要将这个元素在所有层的节点全部删除。首先按照跳表的搜索方式找到要删除的节点,如果可以找到,此时搜索到的节点对应要删除节点的最高层;从待删除节点的最高层往下,将每一层的待删除节点都删除掉,删除方式就是让待删除节点的前一节点直接指向待删除节点的后一节点。具体流程如下图所示:

        跳表的删除的相关代码如下所示:

public boolean erase(int num) {SkiplistNode curNode = headNodes.getFirst();//拿到level最高的头结点开始查询while (curNode != null) {if (curNode.next.data == num) {SkiplistNode preDeleteNode = curNode;//preDeleteNode表示待删除节点的前一节点while (true) {//删除当前层的待删除节点,就是让待删除节点的前一节点指向待删除节点的后一节点preDeleteNode.next = curNode.next.next;//当前层删除完后,需要继续删除下一层的待删除节点//这里让preDeleteNode向下移动一层//向下移动一层后,preDeleteNode就不一定是待删除节点的前一节点了preDeleteNode = preDeleteNode.down;//如果preDeleteNode为null,说明已经将底层的待删除节点删除了//此时就结束删除流程,并返回trueif (preDeleteNode == null) {return true;}//preDeleteNode向下移动一层后,需要继续从当前位置向后遍历//直到找到一个preDeleteNode,使得preDeleteNode.next的值等于目标值//此时preDeleteNode就又变成了待删除节点的前一节点while (preDeleteNode.next.data != num) {preDeleteNode = preDeleteNode.next;}}} else if (curNode.next.data > num) {curNode = curNode.down;} else {curNode = curNode.next;}}return false;
}

        关于跳表的增删查就讲到这里,大家有什么问题或者勘误可以在评论区留言,笔者看到都会回复的。        


http://www.hkcw.cn/article/IoatTMClXq.shtml

相关文章

嵌入式硬件篇---龙芯2k1000串口

针对串口错误 “device reports readiness to read but returned no data (Device disconnected or multiple access on port?)” 的排查和解决方法 硬件方面 检查连接 确认串口设备&#xff08;如串口线、连接的模块等&#xff09;与龙芯设备之间的物理连接是否牢固&#xf…

Ubuntu安装Docker命令清单(以20.04为例)

在你虚拟机上完成Ubuntu的下载后打开终端&#xff01;&#xff01;&#xff01; Ubuntu安装Docker终极命令清单&#xff08;以20.04为例&#xff09; # 1. 卸载旧版本&#xff08;全新系统可跳过&#xff09; sudo apt-get remove docker docker-engine docker.io containerd …

数据结构:递归:自然数之和

目录 递归解法 &#x1f539;第一步&#xff1a;定义本质问题 &#x1f539;第二步&#xff1a;分解问题结构 &#x1f539;第三步&#xff1a;定义初始条件 &#x1f539;第四步&#xff1a;递归思想的自然生成 循环解法 &#x1f539;第 1 步&#xff1a;定义问题最小…

Pandas 技术解析:从数据结构到应用场景的深度探索

序 我最早用Python做大数据项目时&#xff0c;接触最早的就是Pandas了。觉得对于IT技术人员而言&#xff0c;它是可以属于多场景的存在&#xff0c;因为它的本身就是数据驱动的技术生态中&#xff0c;对于软件工程师而言&#xff0c;它是快速构建数据处理管道的基石&#xff1…

CRM管理软件的数据可视化功能使用技巧:让数据驱动决策

在当今数据驱动的商业环境中&#xff0c;CRM管理系统的数据可视化功能已成为企业优化客户管理、提升销售效率的核心工具。据企销客研究显示&#xff0c;具备优秀可视化能力的CRM系统&#xff0c;用户决策效率可提升47%。本文将深入解析如何通过数据可视化功能最大化CRM管理软件…

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…

Redis底层数据结构之字典(Dict)

Dict基本结构 Dict我们可以想象成目录&#xff0c;要翻看什么内容&#xff0c;直接通过目录能找到页数&#xff0c;翻过去看。如果没有目录&#xff0c;我们需要一页一页往后翻&#xff0c;这样时间复杂度就与遍历的O(n)一样了&#xff0c;而用了Dict我们就可以在O(1)的时间复杂…

高通SoC阵列服务器

高通SoC阵列服务器是基于高通系统级芯片&#xff08;SoC&#xff09;构建的高密度计算解决方案&#xff0c;核心特点为低功耗、高算力集成与模块化设计&#xff0c;主要应用于边缘计算和云服务场景。以下是其技术特性和应用方向的综合分析&#xff1a; 一、核心技术特性 架构…

Linux系统下Google浏览器无法使用中文输入的临时解决方案

文章目录 前言方案描述Edge如何兼容 前言 这个AlamaLinux的ibus-libpinyin确实是让人琢磨不透&#xff0c;就只是无法在Chrome浏览器中、Edge浏览器中使用&#xff0c;而在VSCode、Xfce4-Terminal中使用良好。尝试了很多办法都没有效果&#xff0c;最后在Reddit里面找到了相应…

【AI论文】空间多模态大型语言模型(Spatial-MLLM):增强基于视觉的空间智能中多模态大型语言模型(MLLM)的能力

摘要&#xff1a;多模态大语言模型&#xff08;MLLM&#xff09;的最新进展显著提高了2D视觉任务的性能。 然而&#xff0c;提高他们的空间智能仍然是一个挑战。 现有的3D MLLM总是依赖于额外的3D或2.5D数据来加入空间感知&#xff0c;限制了它们在只有2D输入&#xff08;如图像…

黑马Java面试笔记之 微服务篇(业务)

一. 限流 你们项目中有没有做过限流?怎么做的? 为什么要限流呢? 一是并发的确大(突发流量) 二是防止用户恶意刷接口 限流的实现方式: Tomcat:可以设置最大连接数 可以通过maxThreads设置最大Tomcat连接数,实现限流,但是适用于单体架构 Nginx:漏桶算法网关,令牌桶算法自定…

AWS App Mesh实战:构建可观测、安全的微服务通信解决方案

摘要&#xff1a;本文详解如何利用AWS App Mesh统一管理微服务间通信&#xff0c;实现精细化流量控制、端到端可观测性与安全通信&#xff0c;提升云原生应用稳定性。 一、什么是AWS App Mesh&#xff1f; AWS App Mesh 是一种服务网格&#xff08;Service Mesh&#xff09;解…

《云原生安全攻防》-- K8s网络策略:通过NetworkPolicy实现微隔离

默认情况下&#xff0c;K8s集群的网络是没有任何限制的&#xff0c;所有的Pod之间都可以相互访问。这就意味着&#xff0c;一旦攻击者入侵了某个Pod&#xff0c;就能够访问到集群中任意Pod&#xff0c;存在比较大的安全风险。 在本节课程中&#xff0c;我们将详细介绍如何通过N…

吃透 Golang 基础:数据结构之 Map

文章目录 Map概述初始化删除访问不存在的 key 返回 value 的零值遍历 mapmap 自身的零值map 索引时返回的第二个参数使用 map 实现 set Map Hash Map 是无序的 key/value 对集合&#xff0c;其中所有的 key 都是不同的。通过给定的 key 可以在常数时间复杂度内完成检索、更新或…

手机邮箱APP操作

收发电子邮件方式 邮箱可以在网络段登录&#xff0c;也可以在手机端登录。 大学网络服务 收发电子邮件有三种方式&#xff1a; 1、Web方式&#xff1a; 1&#xff09;登录“网络服务”&#xff08;https://its.pku.edu.cn&#xff09;&#xff0c;点页面顶端“邮箱”。 2&…

Spring AI之RAG入门

目录 1. 什么是RAG 2. RAG典型应用场景 3. RAG核心流程 3.1. 检索阶段 3.2. 生成阶段 4. 使用Spring AI实现RAG 4.1. 创建项目 4.2. 配置application.yml 4.3. 安装ElasticSearch和Kibana 4.3.1. 安装并启动ElasticSearch 4.3.2. 验证ElasticSearch是否启动成功 …

Spring AI Alibaba + Nacos 动态 MCP Server 代理方案

作者&#xff1a;刘宏宇&#xff0c;Spring AI Alibaba Contributor 文章概览 Spring AI Alibaba MCP 可基于 Nacos 提供的 MCP server registry 信息&#xff0c;建立一个中间代理层 Java 应用&#xff0c;将 Nacos 中注册的服务信息转换成 MCP 协议的服务器信息&#xff0c…

19-项目部署(Linux)

Linux是一套免费使用和自由传播的操作系统。说到操作系统&#xff0c;大家比较熟知的应该就是Windows和MacOS操作系统&#xff0c;我们今天所学习的Linux也是一款操作系统。 我们作为javaEE开发工程师&#xff0c;将来在企业中开发时会涉及到很多的数据库、中间件等技术&#…

2025.6.3学习日记 Nginx 基本概念 配置 指令 文件

1.初始nginx Nginx&#xff08;发音为 “engine x”&#xff09;是一款高性能的开源 Web 服务器软件&#xff0c;同时也具备反向代理、负载均衡、邮件代理等功能。它由俄罗斯工程师 Igor Sysoev 开发&#xff0c;最初用于解决高并发场景下的性能问题&#xff0c;因其轻量级、高…

SpringCloud——Nacos注册中心、OpenFeign

一、Nacos注册中心 1.注册中心原理 2.服务注册 添加依赖&#xff1a; <!--nacos 服务注册发现--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dep…