【Redis】基础数据结构-skiplist跳跃表

有序集合Sorted Set

zadd

zadd用于向集合中添加元素并且可以设置分值,比如添加三门编程语言,分值分别为1、2、3:

127.0.0.1:6379> zadd language 1 java
(integer) 1
127.0.0.1:6379> zadd language 2 c++
(integer) 1
127.0.0.1:6379> zadd language 3 python
(integer) 1

zrange

zrange根据分值区间返回符合条件的数据:

127.0.0.1:6379> zrange language 1 3 withscores
1) "c++"
2) "2"
3) "python"
4) "3"

zscore

zscore根据key和元素值返回元素的分值

127.0.0.1:6379> zscore language python
"3"

Sorted Set是Redis中的一种数据结构,它可以用来存储带有分值的元素,并且根据分值进行排序,是一个有序的集合。

Sorted Set的结构定义如下,它包含了一个哈希表dict和一个跳跃表zskiplist,其中哈希表可以在O(1)的时间复杂度内进行元素查找,而跳跃表可以支持高效的范围查询:

typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

跳跃表

如果一个有序集合中包含的元素数量比较多或者有序集合中的元素是比较长的字符串时,Redis就会使用跳跃表作为有序集合的底层实现。

跳跃表是一种多层的有序链表,在一个普通的有序链表中如果想要查找某个元素,必须遍历链表,时间复杂度为O(n),那么如何提高查找效率呢,可以使用跳跃表,从列表中抽出一些元素进行分层,比如每隔一个节点就抽出一层:

此时如果需要查找元素为9的节点:

  1. 从第三层开始查找,元素的值为8,因为9大于8并且8之后没有其他的节点所以接下来进入第二层
  2. 进入第二层,8的下一个节点为15,9小于15,所以进入第一层
  3. 进入第一层,获取8的下一个节点,等于要查找的值9,查找结束

结构定义

跳跃表的结构定义

  • header:指向跳跃表中节点的头指针,跳跃表中的节点定义为zskiplistNode,跳跃表实际上也是一个链表,所以会有一个头结点
  • tail:指向跳跃表中节点的尾指针
  • length:跳跃表中节点的数量
  • level:跳跃表的层级
// 跳跃表
typedef struct zskiplist {// 指向跳跃表的头尾指针struct zskiplistNode *header, *tail;// 长度unsigned long length;// 层级int level;
} zskiplist;

节点的结构定义

  • ele:一个sds类型的变量,存储实际的数据
  • score:存储数据的分值,跳跃表就是按照这个分值进行排序的
  • backward:一个指向前一个节点的指针,为了便于从后往前查找
  • zskiplistLevel:一个层级数组,因为跳跃表可以有多层,每一层中都有一个指向当前层级中的下一个节点的指针forward和span跨度,跨度代表了当前层级里面,当前节点与下一个节点直接跨越了几个节点
//跳跃表中的节点结构定义
typedef struct zskiplistNode {// 存储的元素sds ele;// 分值double score;// 后向指针,指向当前节点的前一个节点struct zskiplistNode *backward;// 层级数组struct zskiplistLevel {// 指向当前层级中的下一个节点struct zskiplistNode *forward;// 跨度unsigned long span;} level[];
} zskiplistNode;

跳跃表的创建

/* 创建跳跃表节点*/
zskiplistNode *zslCreateNode(int level, double score, sds ele) {// 分配内存zskiplistNode *zn =zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));zn->score = score;zn->ele = ele;return zn;
}/* 创建跳跃表 */
zskiplist *zslCreate(void) {int j;zskiplist *zsl;// 跳跃表分配内存zsl = zmalloc(sizeof(*zsl));// 层级初始化为1zsl->level = 1;// 长度为0zsl->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;}// 头结点的下一个节点指向NULLzsl->header->backward = NULL;// 尾节点zsl->tail = NULL;return zsl;
}

跳跃表层数的设置

跳跃表根据什么规则来进行层数划分呢,有以下几种方案:

方案一

每隔一个节点,就取出一个节点作为新的一层的节点,这样每一层上节点的数量大约是下一层节点数的一半,此时类似于二分查找,查找复杂度为O(logn)

优点:查找的时间复杂度降低了

缺点:由于需要维护每个层级的节点数,在节点进行插入或者删除的时候,要调整层级节点,带来额外的开销

方案二

新增加节点的时候,调用随机生成层数方法,随机生成一个当前跳跃表所需要的层数,如果生成的层数等于当前层数,新节点只需要加入跳跃表中即可,不需要额外的维护每一个层级的节点数,Redis中就是使用的随机生成层数的方式维护跳跃表的层级。

随机生成层数方法:

#define ZSKIPLIST_MAXLEVEL 32  // 最大层级不超过32
#define ZSKIPLIST_P 0.25 // 随机生成层数
int zslRandomLevel(void) {int level = 1;// 如果生成的随机数的值小于ZSKIPLIST_P,层数就+1while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))level += 1;// 是否超过了最大层数,超过就使用最大层数return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

0xFFFF = 65535

random()&0xFFFF运算之后会生成一个0和65535之间的数,ZSKIPLIST_P * 0xFFFF = 0.25 * 65535,所以random()&0xFFFF 小于 0.25 * 65535的概率为25%,也就是层数会增加1的概率不超过25%。

跳跃表增加节点

  1. 因为跳跃表有多层,所以需要遍历每一层,寻找每层要插入的位置,update[i]就记录了每一层要插入的位置
  2. 随机生成跳跃表的层数,如果层数有变化,则需要调整跳跃表的层高
  3. 创建节点,并将节点插入到跳跃表中
  4. 设置backward,新插入节点的前一个节点是update[0],如果update[0]为头结点,当前节点的前一个节点设为null,否则backward设置为update[0]
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;unsigned int rank[ZSKIPLIST_MAXLEVEL];int i, level;serverAssert(!isnan(score));//获取头结点x = zsl->header;/* 寻找每层要插入的位置,从高层开始向下遍历 */for (i = zsl->level-1; i >= 0; i--) {// rank[i]记录了当前层从header节点到update[i]节点所经历的步长rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];// 如果当前层级下一个节点不为空 并且 下一个节点的score小于要插入节点的分值 或者 下一个节点的score等于要插入节点的score并且对比两个节点存储的元素值之后小于0(字符串比较)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]的值rank[i] += x->level[i].span;// 获取下一个节点x = x->level[i].forward;}// 记录每层需要插入的位置update[i] = x;}// 随机生成跳跃表的层数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;}/* 更新跨度 */for (i = level; i < zsl->level; i++) {update[i]->level[i].span++;}// 设置当前节点的前一个节点,如果update[0]为头结点,当前节点的前一个节点设为null,否则backward设置为update[0]x->backward = (update[0] == zsl->header) ? NULL : update[0];if (x->level[0].forward)x->level[0].forward->backward = x;elsezsl->tail = x;// 增加长度zsl->length++;return x;
}

总结

  1. Sorted Set支持在添加元素的时候为元素添加一个分值,并根据分值排序,是一个有序的集合。
  2. Sorted Set在数据比较少的时候采用ziplist存储,超过阈值后使用哈希表和跳跃表来提高查找效率,其中哈希表用于单值查询,跳跃表用于范围查询。
  3. 跳跃表是一个多层的有序链表,它采用了空间换时间的方式将查找的时间复杂度降到了O(logN)。

参考

黄健宏《Redis设计与实现》

陈雷《Redis5设计与源码分析》

极客时间 - Redis源码剖析与实战(蒋德钧)

【unix21】redis源码分析–zslRandomLevel位运算解析

【有梦想的肥宅】Redis5设计与源码分析读后感(三)跳跃表

Redis版本:redis-6.2.5

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Java-LangChain:使用 ChatGPT API 搭建系统-2】语言模型,提问范式与 Token

第二章 语言模型&#xff0c;提问范式与 Token 在本章中&#xff0c;我们将和您分享大型语言模型&#xff08;LLM&#xff09;的工作原理、训练方式以及分词器&#xff08;tokenizer&#xff09;等细节对 LLM 输出的影响。我们还将介绍 LLM 的提问范式&#xff08;chat format…

【图像处理】使用各向异性滤波器和分割图像处理从MRI图像检测脑肿瘤(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

实验3.2 分期付款计算器

目录 实验目的‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬ 实验内容‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬…

20231005使用ffmpeg旋转MP4视频

20231005使用ffmpeg旋转MP4视频 2023/10/5 12:21 百度搜搜&#xff1a;ffmpeg 旋转90度 https://zhuanlan.zhihu.com/p/637790915 【FFmpeg实战】FFMPEG常用命令行 https://blog.csdn.net/weixin_37515325/article/details/127817057 FFMPEG常用命令行 5.视频旋转 顺时针旋转…

python爬虫基于管道持久化存储操作

文章目录 基于管道持久化存储操作scrapy的使用步骤1.先转到想创建工程的目录下&#xff1a;cd ...2.创建一个工程3.创建之后要转到工程目录下4.在spiders子目录中创建一个爬虫文件5.执行工程setting文件中的参数 基于管道持久化存储的步骤&#xff1a;持久化存储1&#xff1a;保…

集合(容器)-List接口及实现类

容器的特征&#xff1a;①数据长度可变&#xff1b;②数据保存方式不同。 集合体系概述&#xff1a;JAVA的集合框架是由很多接口、抽象类、具体类组成。都位于java.util包中。 Java中集合类中默认可以存储任意数据类型&#xff0c;Java中的集合提供泛型机制&#xff0c;在定义…

李沐深度学习记录3:11模型选择、欠拟合和过拟合

通过多项式拟合探索欠拟合与过拟合 import math import numpy as np import torch from torch import nn from d2l import torch as d2l#生成数据集 max_degree 20 # 多项式的最大阶数 n_train, n_test 100, 100 # 训练和测试数据集大小 true_w np.zeros(max_degree) # …

园林园艺服务经营小程序商城的作用是什么

园林园艺属于高单价服务&#xff0c;同时还有各种衍生服务&#xff0c;对企业来说&#xff0c;多数情况下都是线下生意拓展及合作等&#xff0c;但其实线上也有一定深度&#xff0c;如服务售卖或园艺产品售卖等。 基于线上发展可以增强获客引流、品牌传播、产品销售经营、会员…

很普通的四非生,保研破局经验贴

推免之路 个人情况简介夏令营深圳大学情况机试面试结果 预推免湖南师范大学面试结果 安徽大学面试结果 北京科技大学笔试面试结果 合肥工业大学南京航空航天大学面试结果 暨南大学东北大学 最终结果一些建议写在后面 个人情况简介 教育水平&#xff1a;某中医药院校的医学信息…

STL-stack、queue和priority_queue的模拟实现

目录 一、容器适配器 &#xff08;一&#xff09;什么是适配器 &#xff08;二&#xff09;stack和queue的底层结构 二、Stack 三、queue 四、deque双端队列 &#xff08;一&#xff09;优点 &#xff08;二&#xff09;缺陷 五、优先级队列 &#xff08;一&#xff…

成都建筑模板批发市场在哪?

成都作为中国西南地区的重要城市&#xff0c;建筑业蓬勃发展&#xff0c;建筑模板作为建筑施工的重要材料之一&#xff0c;在成都也有着广泛的需求。如果您正在寻找成都的建筑模板批发市场&#xff0c;广西贵港市能强优品木业有限公司是一家值得关注的供应商。广西贵港市能强优…

华为云云耀云服务器L实例评测|Ubuntu云锁防火墙安装搭建使用

华为云云耀云服务器L实例评测&#xff5c;Ubuntu安装云锁防火墙对抗服务器入侵和网络攻击 1.前言概述 华为云耀云服务器L实例是新一代开箱即用、面向中小企业和开发者打造的全新轻量应用云服务器。多种产品规格&#xff0c;满足您对成本、性能及技术创新的诉求。云耀云服务器L…

基于阴阳对优化的BP神经网络(分类应用) - 附代码

基于阴阳对优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于阴阳对优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.阴阳对优化BP神经网络3.1 BP神经网络参数设置3.2 阴阳对算法应用 4.测试结果&#x…

数据结构与算法--算法

这里写目录标题 线性表顺序表链表插入删除算法 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 线性表 顺序表 链表 插入删除算法 步骤 1.通过循环到达指定位置的前一个位置 2.新建…

VS的调式技巧你真的掌握了吗?

目录 什么是bug? 调式是什么&#xff1f;有多重要&#xff1f; 调试是什么&#xff1f; 调试的基本步骤 debug和release的介绍 windows环境调试介绍 1.调试环境的准备 2.学会快捷键 F11 VS F10 F9 & F5 3.调试时查看程序当前信息 查看临时变量的值 查看内存信…

【物联网】STM32的中断机制不清楚?看这篇文章就足够了

在嵌入式系统中&#xff0c;中断是一种重要的机制&#xff0c;用于处理来自外部设备的异步事件。STM32系列微控制器提供了强大的中断控制器&#xff0c;可以方便地处理各种外部中断和内部中断。本文将详细介绍STM32中断的结构和使用方法。 文章目录 1. 什么叫中断2. 中断优先级…

<学习笔记>从零开始自学Python-之-常用库篇(十二)Matplotlib

Matplotlib 是Python中类似 MATLAB的绘图工具&#xff0c;Matplotlib是Python中最常用的可视化工具之一&#xff0c;可以非常方便地创建2D图表和一些基本的3D图表&#xff0c;可根据数据集&#xff08;DataFrame&#xff0c;Series&#xff09;自行定义x,y轴&#xff0c;绘制图…

IntelliJ IDEA配置Cplex12.6.3详细步骤

Cplex12.6.3版IntelliJ IDEA配置详细步骤 一、Cplex12.6.3版下载地址二、Cplex安装步骤三、IDEA配置CPLEX3.1 添加CPLEX安装目录的cplex.jar包到项目文件中3.2 将CPLEX的x64_win64文件夹添加到IDEA的VM options中 四、检查IDEA中Cplex是否安装成功卸载Cplex 一、Cplex12.6.3版下…

Docker通过Dockerfile创建Redis、Nginx--详细过程

创建Nginx镜像 我们先创建一个目录&#xff0c;在目录里创建Dockerfile [rootdocker-3 ~]# mkdir mynginx [rootdocker-3 ~]# cd mynginx [rootdocker-3 ~]# vim Dockerfile Dockerfile的内容 FROM daocloud.io/library/centos:7 RUN buildDepsreadline-devel pcre-devel o…

代码:对鱼眼相机图像进行去畸变处理

图像投影模型&#xff1a;针孔[fx, fy, cx, cy] 图像畸变模型&#xff1a;切向径向畸变[k1, k2, p1, p2] 说明&#xff1a;用于备忘 第一部分是常规的去畸变操作&#xff0c;在已知内参的情况下对鱼眼相机进行去畸变&#xff0c;这里使用的是remap映射在对图像去畸变后&#x…