Redis数据结构之跳表

跳表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。其核心思想就是通过建立多级索引来实现空间换时间。
在Redis中,使用跳表作为Zset的一种底层实现之一,这也是跳表在Redis中的唯一使用场景。

跳表的实现

跳表由zskiplistNode和zskiplist两个结构定义。其中zskiplistNode表示跳跃表的节点,zskiplist则表示跳跃表节点的相关信息。
跳表

zskiplistNode

typedef struct zskiplistNode {sds ele; // 元素值double score; // 分值struct zskiplistNode *backward; // 后退指针struct zskiplistLevel { // 各层信息struct zskiplistNode *forward; // 该层前向指针unsigned long span; // 该层的跨度} level[];
} zskiplistNode;

前进节点

每个层都有一个指向表尾方向的前进指针,用于从表头向表尾方向访问节点。

跨度

记录两个节点之间的距离。跨度是用来计算rank的,在查找某个节点的过程中,将沿途访问过的所有层的跨度累加起来,得到的结果就是目标节点在跳表中的rank。

后退指针

用于表示表尾向表头方向的访问节点,后退节点每次只能后退至前一个节点。

zskiplistList

typedef struct zskiplist {struct zskiplistNode *header, *tail; // 头、尾指针unsigned long length; // 跳表长度int level; // 跳表层数
} zskiplist;

header

指向跳跃表的表头节点。

tail

指向跳跃表的表尾节点。

level

记录当前跳表的长度,即跳表包含节点的数量。

跳表的操作

新建跳表

/* Create a new skiplist. */
zskiplist *zslCreate(void) {int j;zskiplist *zsl;zsl = zmalloc(sizeof(*zsl));zsl->level = 1;zsl->length = 0;zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {zsl->header->level[j].forward = NULL;zsl->header->level[j].span = 0;}zsl->header->backward = NULL;zsl->tail = NULL;return zsl;
}
  • 首先分配内存
  • level设置为1,length设置为0,后退指针设置为null,尾节点设置为null
  • 头指针指向一个高度为32的节点
  • 为头指针的前进节点设置为null,span设置为0

插入节点

/* Insert a new node in the skiplist. Assumes the element does not already* exist (up to the caller to enforce that). The skiplist takes ownership* of the passed SDS string 'ele'. */
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; // update记录每层的应该指向新增节点的节点unsigned long rank[ZSKIPLIST_MAXLEVEL]; // rank记录每层需要更新的span值int i, level;serverAssert(!isnan(score));x = zsl->header;for (i = zsl->level-1; i >= 0; i--) {/* store rank that is crossed to reach the insert position */rank[i] = i == (zsl->level-1) ? 0 : rank[i+1]; // 最高层rank为0,非最高层rank初始化为上一层的rank值while (x->level[i].forward &&(x->level[i].forward->score < score ||(x->level[i].forward->score == score &&sdscmp(x->level[i].forward->ele,ele) < 0))){ // 对分值&元素遍历对比 直至找到合适的位置rank[i] += x->level[i].span;x = x->level[i].forward;}update[i] = x;}/* we assume the element is not already inside, since we allow duplicated* scores, reinserting the same element should never happen since the* caller of zslInsert() should test in the hash table if the element is* already inside or not. */level = zslRandomLevel(); // 根据幂次定律生成响应的层数if (level > zsl->level) { // 对于高出现有的层数,依次遍历,更新rank、后置节点和跨度for (i = zsl->level; i < level; i++) {rank[i] = 0;update[i] = zsl->header;update[i]->level[i].span = zsl->length;}zsl->level = level;}x = zslCreateNode(level,score,ele); // 生成节点for (i = 0; i < level; i++) {x->level[i].forward = update[i]->level[i].forward; // 针对每一层实现节点的插入。新插入的节点x的forward指向updateupdate[i]->level[i].forward = x; // update的forward指向x节点/* update span covered by update[i] as x is inserted here */x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); // 对x节点更新spanupdate[i]->level[i].span = (rank[0] - rank[i]) + 1; // 对update节点更新span}/* increment span for untouched levels */for (i = level; i < zsl->level; i++) { // 所有高出的层级更新span++update[i]->level[i].span++;}x->backward = (update[0] == zsl->header) ? NULL : update[0];if (x->level[0].forward)x->level[0].forward->backward = x;elsezsl->tail = x; // 更新zsl的尾节点zsl->length++; // 更新zsl的长度return x;
}

更新节点分值

/* Update the score of an element inside the sorted set skiplist.* Note that the element must exist and must match 'score'.* This function does not update the score in the hash table side, the* caller should take care of it.** Note that this function attempts to just update the node, in case after* the score update, the node would be exactly at the same position.* Otherwise the skiplist is modified by removing and re-adding a new* element, which is more costly.** The function returns the updated element skiplist node pointer. */
zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; // 记录需要更新节点在每一层的位置int i;/* We need to seek to element to update to start: this is useful anyway,* we'll have to update or remove it. */x = zsl->header;for (i = zsl->level-1; i >= 0; i--) {while (x->level[i].forward &&(x->level[i].forward->score < curscore ||(x->level[i].forward->score == curscore &&sdscmp(x->level[i].forward->ele,ele) < 0))){x = x->level[i].forward;}update[i] = x;}/* Jump to our element: note that this function assumes that the* element with the matching score exists. */x = x->level[0].forward;serverAssert(x && curscore == x->score && sdscmp(x->ele,ele) == 0);/* If the node, after the score update, would be still exactly* at the same position, we can just update the score without* actually removing and re-inserting the element in the skiplist. */if ((x->backward == NULL || x->backward->score < newscore) &&(x->level[0].forward == NULL || x->level[0].forward->score > newscore)){ // 如果针对最后一个节点的更新分值变大或者对第一个节点的更新分值减小,可以直接更新分值即可,无需移动节点x->score = newscore;return x;}/* No way to reuse the old node: we need to remove and insert a new* one at a different place. */zslDeleteNode(zsl, x, update); // 首先将旧分值节点删除zskiplistNode *newnode = zslInsert(zsl,newscore,x->ele); // 为新分值新建一个新的节点,并插入/* We reused the old node x->ele SDS string, free the node now* since zslInsert created a new one. */x->ele = NULL;zslFreeNode(x);return newnode;
}

根据排名获取节点

/* Finds an element by its rank from start node. The rank argument needs to be 1-based. */
zskiplistNode *zslGetElementByRankFromNode(zskiplistNode *start_node, int start_level, unsigned long rank) {zskiplistNode *x;unsigned long traversed = 0;int i;x = start_node;for (i = start_level; i >= 0; i--) { // 从最高层开始查找,如果上层找到直接return,否则进入下一层while (x->level[i].forward && (traversed + x->level[i].span) <= rank){traversed += x->level[i].span; // 每一层更新已查找的排名x = x->level[i].forward;}if (traversed == rank) { // 对比排名return x;}}return NULL; // 最终不存在
}

删除节点

/* Internal function used by zslDelete, zslDeleteRangeByScore and* zslDeleteRangeByRank. */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {int i;for (i = 0; i < zsl->level; i++) {if (update[i]->level[i].forward == x) {update[i]->level[i].span += x->level[i].span - 1;update[i]->level[i].forward = x->level[i].forward;} else {update[i]->level[i].span -= 1;}}if (x->level[0].forward) {x->level[0].forward->backward = x->backward;} else {zsl->tail = x->backward;}while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)zsl->level--;zsl->length--;
}

Zset获取元素的分值

Zset跳表和字典
Zset除了使用zskiplist来实现之外,结构中还使用字典为有序集合创建了一个成员到分值的映射。字典中的每个键值对都保存了一个集合元素,其中键保存了元素的成员,值保存了元素的分值。通过字典可以O(1)的实现查看某个元素的分值。zscore命令就是根据这一特性实现的。
另外需要主要的是,虽然zset结构同时使用跳表和字典来保存有序集合元素,但是两种结构通过指针共享相同元素的成员和分值,所以同时使用跳表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存。

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

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

相关文章

SpringBoot 集成 ChatGPT,实战附源码

1 前言 在本文中&#xff0c;我们将探索在 Spring Boot 应用程序中调用 OpenAI ChatGPT API 的过程。我们的目标是开发一个 Spring Boot 应用程序&#xff0c;能够利用 OpenAI ChatGPT API 生成对给定提示的响应。 您可能熟悉 ChatGPT 中的术语“提示”。在 ChatGPT 或类似语…

如何本地搭建个人hMailServer邮件服务并实现远程发送邮件

文章目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpola…

VLAN间路由详细讲解

本次实验拓扑的主要概述以及设计到的相关技术 VLAN技术&#xff1a; VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。 每个VLAN是一个广播域&#xff0c;VLAN内的主机间可以直…

YOLOv8改进 | 2023 | SCConv空间和通道重构卷积(精细化检测,又轻量又提点)

一、本文介绍 本文给大家带来的改进内容是SCConv&#xff0c;即空间和通道重构卷积&#xff0c;是一种发布于2023.9月份的一个新的改进机制。它的核心创新在于能够同时处理图像的空间&#xff08;形状、结构&#xff09;和通道&#xff08;色彩、深度&#xff09;信息&#xf…

数字图像处理(实践篇) 十六 基于分水岭算法的图像分割

目录 一 分水岭算法 二 利用OpenCV实现分水岭算法的过程 三 实践 一 分水岭算法 基于任何灰度图像都可以视为地形表面&#xff0c;其中高强度表示山峰和山丘&#xff0c;而低强度表示山谷。首先&#xff0c;开始用不同颜色的水&#xff08;标签&#xff09;填充每个孤立的山…

医院智能导诊小程序源码 智能导诊源码

医院智能导诊系统、AI智能导诊、现有的ai模型做医院智能导诊、智能就医引导系统、人工智能挂号、医院AI全流程智能导诊系统。 智能导诊 可以根据用户症状描述精准推荐科室及医生智能学习医院历史数据及自动进行科室对照,与医院的系统连接后,患者可直接完成预约。 一、系统概述…

flutter-一个可以输入的数字增减器

效果 参考文章 代码 在参考文章上边&#xff0c;主要是改了一下样式&#xff0c;逻辑也比较清楚&#xff0c;对左右两边添加增减方法。 我在此基础上加了_numcontroller 输入框的监听。 加了数字输入框的控制 keyboardType: TextInputType.number, //设置键盘为数字 inputF…

JavaScript 数据结构

JavaScript 数据结构 目录 JavaScript 数据结构 一、标识符 二、关键字 三、常量 四、变量 每一种计算机编程语言都有自己的数据结构&#xff0c;JavaScript脚本语言的数据结构包括&#xff1a;标识符、常量、变量、保留字等。 一、标识符 标识符&#xff0c;说白了&…

Flutter学习(七)GetX offAllNamed使用的问题

背景 使用GetX开发应用的时候&#xff0c;也可能有人调用过offAllNamed&#xff0c;会发现所有controller的都被销毁了 环境 win10 getx 4.6.5 as 4 现象 从A页面&#xff0c;跳转到B页面&#xff0c;然后调用offAllNamed进行回到A页面&#xff0c;观察controller声明周期…

如何从 Android 手机恢复已删除的视频

您是否曾经丢失过手机中的任何数据&#xff1f;如今&#xff0c;由于 Android 上的应用程序崩溃、根进程停止、Android 更新失败等等&#xff0c;数据丢失很普遍。错误删除是丢失视频、录音和音乐副本的另一种可能的方式。 丢失包含有关新完成的项目的重要信息的视频或婚礼、周…

零基础OpenAi应用商店开发

在本月OpenAi开发者大会上&#xff0c;OpenAI宣布推出了GPTs功能&#xff0c;也就是GPT Store&#xff0c;类似App Store的应用商店&#xff0c;任何用户都可以去参与创建应用。通过该功能&#xff0c;用户可以定制化打造自己的GPT&#xff0c;并公开分享至OpenAI的应用商店。定…

12.1 二叉树简单题

101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 思路&#xff1a;对称二叉树 有一个特点是以 中左右顺序遍历左子树的结果会等于 中右左顺序遍历右子树的结果…

服务器中深度学习环境的配置

安装流程 11.17 日&#xff0c;周末去高校参加学术会议&#xff0c;起因&#xff0c; 由于使用了某高校内的公共有线网络&#xff0c; 远程连接服务器后&#xff0c;黑客利用 ssh 开放的 22 端口&#xff0c; 篡改了主机的配置&#xff0c; 使得只要一连上网络&#xff0c; 服…

接口测试基础知识

一、接口测试简介 什么是接口测试&#xff1f; 接口测试是测试系统组件间接口的一种测试&#xff0c;主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。 测试的重点&#xff1a; 检查数据的交换&#xff0c;传递和控制管理过程&#xff1b;检查系统间的相互…

『吴秋霖赠书活动 | 第五期』《Kubernetes原生微服务开发》

【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋友可以关注《爬虫JS逆向实战》&#xff0c;对分布式爬虫平…

企业加密软件有哪些(公司防泄密软件)

企业加密软件是专门为企业设计的软件&#xff0c;旨在保护企业的敏感数据和信息安全。这些软件通过使用加密技术来对数据进行加密&#xff0c;使得数据在传输和存储过程中不会被未经授权的人员获取和滥用。 企业加密软件的主要功能包括数据加密、文件加密、文件夹加密、移动设备…

深度学习第4天:感知机模型

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​ 文章目录 感知机模型介绍 神经网络搭建感知机 结构 准备训练数据 感知机的损失函数与优化方法 测试结果 完整代码 多层感知机 结语 感知机模…

优彩云采集器最新版免费下载,优彩云采集器免费

随着网络时代的发展&#xff0c;SEO&#xff08;Search Engine Optimization&#xff0c;搜索引擎优化&#xff09;已经成为网站推广和营销的关键一环。在SEO的世界里&#xff0c;原创内容的重要性愈发凸显。想要做到每天更新大量原创文章&#xff0c;并不是一件轻松的事情。优…

mybatis源码(五)springboot pagehelper实现查询分页

1、背景 springboot的pagehelper插件能够实现对mybatis查询的分页管理&#xff0c;而且在使用时只需要提前声明即可&#xff0c;不需要修改已有的查询语句。使用如下&#xff1a; 之前对这个功能一直很感兴趣&#xff0c;但是一直没完整看过&#xff0c;今天准备详细梳理下。按…

大数据湖项目建设方案:文档全文101页,附下载

关键词&#xff1a;大数据解决方案&#xff0c;数据湖解决方案&#xff0c;数据治理解决方案&#xff0c;数据中台解决方案 一、大数据湖建设思路 1、明确目标和定位&#xff1a;明确大数据湖的目标和定位是整个项目的基础&#xff0c;这可以帮助我们确定项目的内容、规模、所…