Redis数据结构之跳跃表(SkipList)

Redis是一个开源的、使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis凭借其高性能、高可用性、丰富的数据结构以及简洁的API而备受青睐。其中,跳跃表(SkipList)作为Redis中的一种重要数据结构,被广泛用于有序集合(Sorted Set)的底层实现,以支持高效的插入、删除和查找操作。

什么是跳跃表(SkipList)

跳跃表(SkipList)是一种用于有序元素序列快速搜索随机化的数据结构,由美国计算机科学家William Pugh于1989年在其论文《Skip lists: a probabilistic alternative to balanced trees》中提出。它通过在每个节点中维持多个指向其他节点的指针,以达到快速访问节点的目的。跳跃表可以看作是对单链表的一种优化,通过添加多级索引来提高查找效率。

跳跃表的基本结构

跳跃表是一个包含多个节点和层级信息的结构体。每个节点都包含多个层,每一层都构成了一个有序链表。查找时,从最高层开始,根据目标值的大小逐层向下查找,直到找到目标节点或确定目标节点不存在。

跳跃表节点(zskiplistNode)

跳跃表的节点结构通常包含以下信息:

  • 成员对象(ele):存储节点的元素值。
  • 分数(score):用于排序和比较的元素分数,确保有序性。
  • 后退指针(backward):指向前一个节点的指针,便于反向遍历。
  • 层级数组(level):一个柔性数组,每个元素都是一个结构体,包含指向同一层次和下一层次节点的指针(forward)和跨度(span)。

跳跃表(zskiplist)

包含多个节点和层级信息的结构体,通常还包含表头节点(header)、表尾节点(tail)以及最大层级(level)等信息。表头节点和表尾节点用于快速定位跳跃表的边界,而最大层级则决定了跳跃表的高度。

跳跃表的优势

  1. 查找效率高:对于包含n个节点的跳跃表,其查找操作的时间复杂度为O(log n),这是因为每次查找都可以跳过部分节点,从而减少比较次数。
  2. 实现简单:与平衡树相比,跳跃表的实现更加简单直观,插入和删除操作只需要修改相邻节点的指针,不涉及复杂的子树调整。
  3. 空间换时间:通过增加数据冗余(即多层索引),跳跃表实现了查找效率的显著提升。

Redis中的跳跃表实现

Redis中的跳跃表主要用于有序集合(Sorted Set)的底层实现。有序集合是一种元素不重复且按照成员分数进行排序的集合。Redis使用跳跃表来维护有序集合中的元素顺序,以便实现高效的插入、删除和查找操作。

Redis跳跃表节点的C语言结构体定义

typedef struct zskiplistNode {sds ele; // 节点元素double score; // 节点分数struct zskiplistNode *backward; // 回退指针// 层级数组,level 数组中的每个元素都包含两个指针:forward 和 spanstruct zskiplistLevel {struct zskiplistNode *forward;unsigned int span;} level[];
} zskiplistNode;

跳跃表操作的详细代码示例

以下是一个简化的Redis跳跃表插入操作的C语言代码示例。这个示例主要展示了如何创建新的节点、计算新节点的层级、更新跳跃表以及处理插入过程中的一些细节。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 假设一些基础函数已经定义,如zslRandomLevel(), zslCreateNode()等// 插入新元素到跳跃表
int zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;int rank[ZSKIPLIST_MAXLEVEL];int i, level;// 更新update数组和rank数组// 省略具体实现,这里只展示框架level = zslRandomLevel();if (level > zsl->level) {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;update[i]->level[i].forward = x;// 更新跨度x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);update[i]->level[i].span = (rank[0] - rank[i]) + 1;}// 设置后退指针x->backward = (rank[0] == 0) ? NULL : update[0];if (x->level[0].forward != NULL) {x->level[0].forward->backward = x;} else {zsl->tail = x;}// 更新跳跃表的长度zsl->length++;// 返回新插入节点的排名(通常为0,因为是从头开始插入)return 0;
}// 辅助函数:随机生成节点层级
int zslRandomLevel(void) {int level = 1;while ((random() & 0xFFFF) < (ZSKIPLIST_P * 0xFFFF))level += 1;return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}// 辅助函数:创建新节点
zskiplistNode *zslCreateNode(int level, double score, sds ele) {zskiplistNode *zn = zmalloc(sizeof(*zn) + level * sizeof(struct zskiplistLevel));zn->score = score;zn->ele = ele;for (int i = 0; i < level; i++) {zn->level[i].forward = NULL;zn->level[i].span = 0;}zn->backward = NULL;return zn;
}// 假设的跳跃表结构体定义
typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;// 初始化跳跃表
zskiplist *zslCreate(void) {int j;zskiplist *zsl;zsl = zmalloc(sizeof(*zsl));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;zsl->length = 0;zsl->level = 1;return zsl;
}// 假设的常量定义
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */// 示例使用
int main() {zskiplist *zsl = zslCreate();sds ele1 = sdsnew("one");sds ele2 = sdsnew("two");zslInsert(zsl, 1.0, ele1);zslInsert(zsl, 2.0, ele2);// 假设有额外的函数来遍历和打印跳跃表// printSkipList(zsl);// 清理资源// 省略具体的资源释放代码return 0;
}

注意:上述代码是一个简化的示例,用于说明如何在Redis的上下文中实现跳跃表的插入操作。在实际Redis实现中,跳跃表的操作会更加复杂,包括更详细的错误处理、内存管理以及与其他Redis数据结构的交互等。

此外,Redis的跳跃表实现还包含删除和查找等操作,这些操作同样基于节点的层级和跨度信息来实现高效的遍历和更新。

最后,虽然上述代码使用了C语言的标准库函数(如random(), malloc(), free()等),但在Redis的实际实现中,它使用了一套自定义的内存分配和错误处理机制,以确保高效和稳定的运行。

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

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

相关文章

【RabbitMQ】MQ相关概念

一、MQ的基本概念 定义&#xff1a;MQ全称为Message Queue&#xff0c;是一种提供消息队列服务的中间件&#xff0c;也称为消息中间件。它允许应用程序通过读写队列中的消息来进行通信&#xff0c;而无需建立直接的连接。作用&#xff1a;主要用于分布式系统之间的通信&#x…

SQL Server数据库管理(五)从权限管理到数据恢复的全面指南

文章目录 SQL Server数据库管理&#xff1a;从权限管理到数据恢复的全面指南引言第一章&#xff1a;权限管理1.1 SQL Server的安全机制1.2 身份验证模式1.3 登录权限设置1.3.1 创建登录账户1.3.2 服务器级别权限 1.4 数据库级别权限1.5 对象级别权限1.6 实验&#xff1a;权限设…

CANoe在使用时碰到的一些很少见的Bug

CANoe作为一款成熟且稳定的总线仿真与测试工具&#xff0c;深受汽车工程师们的喜爱。CANoe虽然稳定&#xff0c;但作为一个软件来说&#xff0c;在使用中总会出现一些或大或小的Bug。最近全球范围内的大规模蓝屏事件&#xff0c;是由某个安全软件引起的。而很多CANoe使用者最近…

Java spring security 自定义登录逻辑实现

介绍 在使用框架自带的Security的登录认证时&#xff0c;默认只能使用用户名去查询&#xff0c;如果有业务需要其他字段也需要进行查询&#xff0c;只能采用根据用户名去找到对应的数据。 自定义鉴权接口CustomUsernamePasswordAuthenticationToken /*** author wuzhenyong* C…

【中项】系统集成项目管理工程师-第7章 软硬件系统集成-7.2基础设施集成

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

【React】详解classnames工具:优化类名控制的全面指南

文章目录 一、classnames的基本用法1. 什么是classnames&#xff1f;2. 安装classnames3. 导入classnames4. classnames的基本示例 二、classnames的高级用法1. 动态类名2. 传递数组3. 结合字符串和对象4. 结合数组和对象 三、实际应用案例1. 根据状态切换类名2. 条件渲染和类名…

Kafka消息队列

目录 什么是消息队列 高可用性 高扩展性 高可用性 持久化和过期策略 consumer group 分组消费 ZooKeeper 什么是消息队列 普通版消息队列 说白了就是一个队列,生产者生产多少,放在消息队列中存储,而消费者想要多少拿多少,按序列号消费 缓存信息 生产者与消费者解耦…

VulnHub靶机入门篇--Kioptrix4

1.环境配置 下载地址&#xff1a; https://download.vulnhub.com/kioptrix/Kioptrix4_vmware.rar 下载完解压之后是一个vdmk文件&#xff0c;我们需要先创建一个新的虚拟机&#xff0c;将vdmk文件导入就行了 先移除原先硬盘&#xff0c;然后再进行添加&#xff0c;网络连接为…

EV代码签名证书具体申请流程

EV&#xff08;扩展验证&#xff09;代码签名证书是一种用于对代码进行数字签名的安全证书&#xff0c;它可以帮助用户验证软件发布者的身份&#xff0c;并确保软件未被篡改。对于Windows硬件开发者来说&#xff0c;这种证书尤其重要&#xff0c;因为它可以用来注册Windows硬件…

【Golang 面试 - 基础题】每日 5 题(八)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

【DP】01背包

算法-01背包 前置知识 DP 思路 01背包一般分为两种&#xff0c;不妨叫做价值01背包和判断01背包。 价值01背包 01背包问题是这样的一类问题&#xff1a;给定一个背包的容量 m m m 和 n n n 个物品&#xff0c;每个物品有重量 w w w 和价值 v v v&#xff0c;求不超过背…

unity 导出 资源 -- 的 Player Settings... Inspector ----配置文件

--------------------------- unity 导出 资源 -- 的 Player Settings... Inspector ----配置文件名称--------OK 配置 文件位置&#xff1a;E:\BL\client\ProjectSettings\ProjectSettings.asset 具体操作&#xff1a; 复制一个备份配置 ------.unity--File--Build-Setting…

六、2 写PWM代码(函数介绍、呼吸灯代码)

目录 一、1、步骤 2、函数介绍 3、外设引脚和GPIO引脚的复用关系&#xff08;引脚定义表&#xff09; 二、1、呼吸灯 步骤 &#xff08;1&#xff09;初始化通道 1&#xff09;输出比较模式 2&#xff09;输出比较极性 &#xff08;2&#xff09;配置GPIO &#xff08…

Zabbix 部署 - docker

考虑方便移植&#xff0c;多环境部署&#xff0c;整体采用 docker-compose 方式部署 docker-compose 总共4个服务&#xff0c;数据库 后台服务 前端服务 Agent version: 3.7 services:zabbix-mysql:container_name: zabbix-mysqlimage: mysql:5.7.40restart: alwaysenviro…

肆[4],VisionMaster全局触发测试说明

1&#xff0c;环境 VisionMaster4.3 2&#xff0c;实现功能 2.1&#xff0c;全局触发进行流程控制执行。 2.2&#xff0c;取像完成&#xff0c;立即运动到下一个位置&#xff0c;同步进行图片处理。 2.3&#xff0c;发送结果的同时&#xff0c;还需要显示图像处理的痕迹。 …

如何运行别人的vue项目

文章目录 如何运行别人的vue项目一、删除现有的node_modules二、npm换源三、清理缓存四、进行依赖安装五、运行服务器 如何运行别人的vue项目 一、删除现有的node_modules 二、npm换源 换成淘宝的镜像源 查看当前镜像源 npm config get registry更换淘宝镜像源 npm confi…

如何在VB中处理异常和错误

在Visual Basic (VB) 中&#xff0c;处理异常和错误是确保程序稳定性和健壮性的重要部分。VB提供了结构化的异常处理机制&#xff0c;允许开发者在代码执行过程中预测并响应可能出现的错误情况。以下是VB中处理异常和错误的基本方法&#xff1a; 1. 使用 Try...Catch...Finall…

H616设计时候存在的问题

1.存在大量孤铜的问题&#xff1a; 这种情况是绝对不允许的&#xff0c;但是GBA焊盘打大量的过孔会出现很多这样的孤铜&#xff1a; 解决办法&#xff1a; 像这种出现大量重复焊盘的&#xff0c;用导线连接起来&#xff0c;之后铺铜形成铜皮&#xff0c;再在这个小铜皮上面打…

全网首创!基于GaitSet的一种多人步态识别方法公示

有源代码V细聊&#xff0c;可商用/私用/毕设等&#xff1a;NzqDssm16 &#x1f349;1 绪论 经过相关研究确认&#xff0c;步态识别是足以达到应用级别的生物识别技术&#xff0c;在现代社会中自始至终都存在着广泛的应用前景。之所以迟迟没有普及&#xff0c;主要是实…

【Oracle 进阶之路】Oracle 简介

一、简述 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性好、使用方便、功能强&…