10.Redis数据结构之跳表

news2024/10/6 15:16:58

sortedSet

sortedSet是Redis提供的一个非常特别的数据结构,常用作排行榜等功能,将用户id作为value,关注时间或者分数作为score进行排序。

与其他数据结构相似,zset也有两种不同的实现,分别是zipList和(hash+skipList)。

编码转换规则

规则如下:

  • zipList满足以下两个条件

    • [score,value]键值对数量少于128个;
    • 每个元素的长度小于64字节;
  • 不满足以上两个条件时使用hash+skipList

    • hash用来存储value到score的映射,这样就可以在O(1)时间内找到value对应的分数
  • skipList按照从小到大的顺序存储分数

    • skipList每个元素的值都是[value,socre]对
    • hash保证快速查找和唯一 skiplist保证有序

每个跳表的节点也都会维护着一个score值,这个值在跳表中是按照从小到大的顺序排列好的。

image.png

压缩列表

ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保存元素的分值(score),压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。

zipList结构如下:

image.png

指针指向的压缩列表表示如下:

image.png

跳表

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。 结构如下:

image.png

  • zset结构中的zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等命令就是基于跳跃表API来实现的
  • zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以用O(1)复杂度查找给定成员的分值,ZSCORE命令就是根据这一特性实现的

有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个double类型的浮点数。值得一提的是,虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存

image.png

其具体实现方式如下:

image.png

字典保证速度,跳表保证顺序

有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现,但无论单独使用字典还是跳跃表,在性能上对比起同时使用字典和跳跃表都会有所降低

  • 如果我们只使用字典来实现有序集合,那么虽然以O(1)复杂度查找成员的分值这一特性会被保留,但是,因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作——比如ZRANK、ZRANGE等命令时,程序都需要对字典保存的所有元素进行排序,完成这种排序需要至少O(NlogN)时间复杂度,以及额外的O(N)内存空间(因为要创建一个数组来保存排序后的元素)
  • 如果我们只使用跳跃表来实现有序集合,那么跳跃表执行范围型操作的所有优点都会被保留,但因为没有了字典,所以根据成员查找分值这一操作的复杂度将从O(1)上升为O(logN)

字典用于快速查找分值,跳跃表用于执行范围操作

使用跳表时的示意图:

跳表skipList在Redis中的运用场景只有一个,那就是作为有序列表zset的底层实现。

跳表可以保证增、删、查等操作时的时间复杂度为O(logN),这个性能可以与平衡树相媲美,但实现方式上却更加简单,唯一美中不足的就是跳表占用的空间比较大,其实就是一种空间换时间的思想。跳表的结构如下所示:

image.png

Redis中跳表一个节点最高可以达到64层,一个跳表中最多可以存储2^64个元素。跳表中,每个节点都是一个skiplistNode,每个跳表的节点也都会维护着一个score值,这个值在跳表中是按照从小到大的顺序排列好的。

跳表的结构定义如下所示:

image.png

header:指向跳表的头节点,通过这个指针可以直接找到表头,时间复杂度为O(1);

tail:指向跳表的尾节点,通过这个指针可以直接找到表尾,时间复杂度为o(1);

length:记录跳表的长度,即不包括头节点,整个跳表中有多少个元素;

level:记录当前跳表内,所有节点中层数最大的level;

zskiplistNode的结构定义如下:

typedf struct zskiplistNode{    sds ele;// 具体的数据    double score;// 分数    struct zskiplistNode *backward;//后退指针    struct zskiplistLevel{          struct zskiplistNode *forward;//前进指针forward        unsigned int span;//跨度span   }level[];//层级数组 最大32 }zskiplistNode;

ele:真正的数据,每个节点的数据都是唯一的,但节点的分数score可以是一样的。两个相同分数score的节点是按照元素的字典序进行排列的;

score:各个节点中的数字是节点所保存的分数score,在跳表中,节点按照各自所保存的分数从小到大排列;

backward:用于从表尾向表头遍历,每个节点只有一个后退指针,即每次只能后退一步;

层级数组:这个数组中的每个节点都有两个属性,forward指向下一个节点,span跨度用来计算当前节点在跳表中的一个排名,这就为zset提供了一个查看排名的方法。

数组中的每个节点中用1、2、3等字样标记节点的各个层,L1代表第一层,L2代表第二层,L3代表第三层;,以此类推;

skiplistNode的示意图如下所示:

image.png

以下图为例,讲解一下skiplist的增删改查过程。

跳表范围查询

image.png

假设现在要查找9这个节点,步骤如下:

  • 从head开始遍历,指针指向10这个节点,由于9<10,且同层的下一个指针指向NULL,所以层级数组要下降一层;
  • 跳到6节点所在的层,同理,6<9,且同层的下一个指针指向10,再下降一层;
  • 跳到8节点所在的层,同理,8<9,且同层的下一个指针指向10,再下降一层;
  • 此时到了第一层,第一层是一个双向链表,由于8<9,所以开始向后遍历,查找到9就返回,不然就返回NULL;

跳表精准查询:hash

根据key在hash表直接找,时间复杂度O(1)。

跳表添加

上面例子中,9个结点,一共4层,可以说是理想的跳跃表了,不过随着我们对跳跃表进行插入/删除结点的操作,那么跳跃表结点数就会改变,意味着跳跃表的层数也会动态改变。

这里我们面临一个问题,就是新插入的结点应该跨越多少层?

这个问题已经有大牛替我们解决好了,采取的策略是通过抛硬币来决定新插入结点跨越的层数:每次我们要插入一个结点的时候,就来抛硬币,如果抛出来的是正面,则继续抛,直到出现负面为止,统计这个过程中出现正面的次数,这个次数作为结点跨越的层数。

通过这种方法,可以尽可能着接近理想的层数。大家可以想一下为啥会这样呢?

插入

例如,我们要插入结点 3,4,通过抛硬币知道3,4跨越的层数分别为 0,2 (层数从0开始算),则插入的过程如下:

插入 3,跨越0层。

image.png

插入 4,跨越2层。

image.png

  • 从 head 节点开始,先是在 head 开始降层来查找到最后一个比 4小的节点;
  • 等到查到最后一个比 4 小的节点的时候(假设为3 );
  • 然后需要引入一个随机层数算法来为这个节点随机地建立层数;
  • 把这个节点插入进去以后,同时更新一遍最高的层数即可;

``` int zslRandomLevel(void) {    int level = 1;    while ((random()&0xFFFF) < (ZSKIPLISTP * 0xFFFF))        level += 1;    return (level MAXLEVEL) ? level : ZSKIPLIST MAXLEVEL; } #define ZSKIPLISTMAXLEVEL 32

define ZSKIPLIST_P 0.25

```

跳表删除

删除的过程前期与查找相似,先定位到元素所在的位置,再进行删除,最后更新一下指针、更新一下最高的层数。

解决了插入之后,我们来看看删除,删除就比较简单了,例如我们要删除4,那我们直接把4及其所跨越的层数删除就行了。

image.png

跳表修改

先是判断这个 value 是否存在,如果存在就是更新的过程,如果不存在就是插入过程。在更新的过程是,如果找到了Value,先删除掉,再新增,这样的弊端是会做两次的搜索,在性能上来讲就比较慢了,在 Redis 5.0 版本中,Redis 的作者 Antirez 优化了这个更新的过程,目前的更新过程是如果判断这个 value是否存在,如果存在的话就直接更新,然后再调整整个跳跃表的 score 排序,这样就不需要两次的搜索过程。

image.png

跳跃表的插入与删除至此都讲完了,总结下跳跃表的有关性质:

(1). 跳跃表的每一层都是一条有序的链表.

(2). 跳跃表的查找次数近似于层数,时间复杂度为O(logn),插入、删除也为 O(logn)。

(3). 最底层的链表包含所有元素。

(4). 跳跃表是一种随机化的数据结构(通过抛硬币来决定层数)。

(5). 跳跃表的空间复杂度为 O(n)。

跳跃表 vs 二叉查找树

有人可能会说,也可以采用二叉查找树啊,因为查找查找树的插入、删除、查找也是近似 O(logn) 的时间复杂度。

不过,二叉查找树是有可能出现一种极端的情况的,就是如果插入的数据刚好一直有序,那么所有节点会偏向某一边。例如

image.png

这种接结构会导致二叉查找树的查找效率变为 O(n),这会使二叉查找树大打折扣。

跳跃表 vs 红黑树

红黑可以说是二叉查找树的一种变形,红黑在查找,插入,删除也是近似O(logn)的时间复杂度,但学过红黑树的都知道,红黑树比跳跃表复杂多了,反正我是被红黑树虐过。在选择一种数据结构时,有时候也是需要考虑学习成本的。

红黑树插入,删除结点时,是通过调整结构来保持红黑树的平衡,比起跳跃表直接通过一个随机数来决定跨越几层,在时间复杂度的花销上是要高于跳跃表的。

当然,红黑树并不是一定比跳跃表差,在有些场合红黑树会是更好的选择,所以选择一种数据结构,关键还得看场合。

总上所述,维护一组有序的集合,并且希望在查找、插入、删除等操作上尽可能快,那么跳跃表会是不错的选择。

redis 中的数据数据便是采用了跳跃表,当然,ridis也结合了哈希表等数据结构,采用的是一种复合数据结构。

跳表 VS B+树

因为B+树的原理是 叶子节点存储数据,非叶子节点存储索引,B+树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。

每次读取磁盘页时就会读取一整个节点,每个叶子节点还有指向前后节点的指针,为的是最大限度的降低磁盘的IO;因为数据在内存中读取耗费的时间是从磁盘的IO读取的百万分之一

而Redis是 内存中读取数据,不涉及IO,因此使用了跳表;

redis使用跳表不用B+树的原因是:redis是内存数据库,而B+树纯粹是为了mysql这种IO数据库准备的。B+树的每个节点的数量都是一个mysql分区页的大小16K

sortedSet VS map

redis的sortedSet采用的是哈希表+跳表。

java的HashMap采用的是(数组+链表+红黑树)。

java的LinkedHashMap采用的是(数组+链表+红黑树) + 双向链表。

java的TreeMap采用的是红黑树。

LinkedHashMap保证了插入顺序的一致性,所以可以保证遍历顺序和存放顺序的一致性。 

TreeMap由于元素插入的时候会根据key进行排序,所以并不能保证遍历顺序和存放顺序的一致性。

感觉sortedSet和java中的各种map没有可比性。原因在于sortedSet可以根据分数和key查找元素。而java中的各种map只能根据key找元素。

个人觉得如果java要模拟实现sortedSet,可以采用HashMap+TreeMap。

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

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

相关文章

打造跨境电商新引擎:揭秘跨境电商系统商城软件平台源码

跨境电商系统商城软件平台的意义与需求 跨境电商正成为全球贸易发展的重要驱动力&#xff0c;然而&#xff0c;建立和运营一个成功的跨境电商平台并非易事。在这个过程中&#xff0c;跨境电商系统商城软件平台的作用日益凸显。 跨境电商系统商城软件平台源码是构建一个完整、高…

关于 ts 这一篇文章就够了

你好 文章目录 一、js 和 ts二、TypeScript的特点三、了解 ts , 爱上 ts &#x1f923; 一、js 和 ts 随着近几年前端领域的快速发展&#xff0c;JavaScript 迅速被普及和受广大开发者的喜爱&#xff0c;借助于 JavaScript 本身的强大&#xff0c;也让使用JavaScript开发的人员…

java八股文面试[JVM]——JVM内存结构2

知识来源&#xff1a; 【2023年面试】JVM内存模型如何分配的_哔哩哔哩_bilibili

PRACK消息

概述 PRACK消息是sip协议的扩展&#xff0c;在RFC3262中定义&#xff0c;标准的名称是sip协议中的可靠临时响应。 本文简单介绍标准中对PRACK消息流程的描述&#xff0c;以及fs配置PRACK的方式。 环境 centos&#xff1a;CentOS release 7.0 (Final)或以上版本 freeswitc…

开放式耳机百元旗舰标杆,性能小钢炮南卡OE CC开放式耳机上线!

近日&#xff0c;南卡再次以领先的姿态推出了百元级性能小钢炮&#xff0c;全新NANK南卡OE-CC耳机。这款耳机不仅延续了南卡一贯的创新精神&#xff0c;更是为用户带来了卓越的音质和性能体验。南卡OE-CC的上线&#xff0c;再次彰显了南卡作为行业先驱者的地位&#xff0c;为用…

中科驭数受邀在招商银行金融科技论坛作异构计算主题分享 解码金融科技先进算力构建之路

8月25日&#xff0c;2023招银浦江金融科技论坛正式召开。中科驭数高级副总裁张宇受邀在资管科技分论坛发表《金融行业先进异构算力底座构建之路》的主题演讲&#xff0c;与参会嘉宾分享了当前计算系统的发展趋势以及如何通过异构算力构建IT技术底座来推动金融科技的创新。 ▲ 中…

行政如何借助ChatGPT提高效率

文章目录 ChatGPT的优势与局限行政人员的核心价值行政人员应对策略借助ChatGPT提高效率结论 ✍创作者&#xff1a;全栈弄潮儿 &#x1f3e1; 个人主页&#xff1a; 全栈弄潮儿的个人主页 &#x1f3d9;️ 个人社区&#xff0c;欢迎你的加入&#xff1a;全栈弄潮儿的个人社区 &a…

WPF基础入门-Class4-WPF绑定

WPF基础入门 Class4&#xff1a;WPF绑定 1、cs文件中设置需要绑定的数据&#xff1a; public partial class Class_4 : Window{public Class_4(){InitializeComponent();List<Color> test new List<Color>();test.Add(new Color() { Code "Yellow",…

Mybatis-Plus 代码自动生成器 整合springboot

Pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 …

修改Jupyter Notebook默认打开路径

这里我是重新下载的anaconda&#xff0c;打开Jupyter之后是默认在C盘的一个路径的&#xff0c;现在我们就来修改一下它的一个默认打开路径&#xff0c;这样在我们后续学习过程中&#xff0c;可以将ipynb后缀的文件放在这个目录下就能查看了。 1、先打开Anaconda Prompt&#x…

【已解决】Windows11给IPad或IPhone拷贝文件

问题&#xff1a; 想要把电脑的PDF学习的文件拷贝到IPad上 解决办法&#xff1a; 电脑以win11为例&#xff0c;语言我调成英文用了&#xff0c;大家可以自行翻译对应。 创建账户 1、新建一个账户用于共享专用。点击settings-Accounts-Others Users-Add account&#xff0c…

【中危】Apache XML Graphics Batik<1.17 存在SSRF漏洞 (CVE-2022-44729)

zhi.oscs1024.com​​​​​ 漏洞类型SSRF发现时间2023-08-23漏洞等级中危MPS编号MPS-2022-63578CVE编号CVE-2022-44729漏洞影响广度极小 漏洞危害 OSCS 描述Apache XML Graphics Batik 是一个开源的、用于处理可缩放矢量图形(SVG)格式图像的工具库。 受影响版本中&#xff0…

Midjourney 完整版教程(从账号注册到设计应用)

目录 一、Midjourney 介绍 二、Midjourney 的AI出图示例 三、手把手教你上手Midjourney 1、账号&初始化 1.1 账号注册登录 1.2 账号付费 1.3 账号初始化 2、Midjourney的基础设置 3、Midjourney 出图步骤。 (一)直接描述出图 (二)垫图生图。 4、Midjourney的…

类加载器的分类

1、几种类加载器的使用体会 JVM支持两种类型的类加载器&#xff0c;分别为引导类加载器&#xff08;Bootstrap ClassLoader&#xff09;和自定义类加载器&#xff08;User-Defined ClassLoader&#xff09;。从概念上来讲&#xff0c;自定义类加载器一般指的是程序中由开发人员…

【java】【项目实战】[外卖二]后台登录与退出功能实现

目录 一 、登录功能开发 1.1 需求分析 1.1.1 查看原型 1.1.2 查看效果图 1.1.3 查看登录请求信息&#xff08;有接口文档最佳啊&#xff09; 1.1.4 数据模型 &#xff08;employee表&#xff09; 1.2 代码开发 1.2.1 创建实体类Employee与employee库表进行映射 1.2.2 创…

Mac操作系统上设置和配置PPPoE连接

嗨&#xff0c;在使用Mac的小伙伴么&#xff01;你是否在Mac操作系统上尝试设置和配置PPPoE连接&#xff0c;却不知道怎么设置&#xff1f;别担心&#xff0c;今天我将为你一步步教你如何在Mac上进行设置和配置。无论你是新手还是有经验的用户&#xff0c;本文都将帮助你轻松完…

Montreal Forced Aligner (MFA)安装教程失败原因集合

写在前面&#xff1a;本文章只考虑2.x版本以后的安装时遇到的问题 推荐大家看官方的安装教程&#xff0c;里面设计不同系统&#xff0c;不同场景的安装方式。 官网安装教程地址&#xff1a;https://montreal-forced-aligner.readthedocs.io/en/latest/installation.html 在安装…

【福利】Google Cloud Next ’23 精彩待发,Cloud Ace 作为联合赞助商提前发福利~

【Cloud Ace 是 Google Cloud 全球战略合作伙伴&#xff0c;在亚太地区、欧洲、南北美洲和非洲拥有二十多个办公室。Cloud Ace 在谷歌专业领域认证及专业知识目前排名全球第一位&#xff0c;并连续多次获得 Google Cloud 各类奖项。作为谷歌云托管服务商&#xff0c;我们提供谷…

centos7设置java后端项目开机自启【脚本、开机自启】

1.切换目录 cd /etc/init.d/2.编辑脚本 vim wbs-service-start.sh编辑内容 #!/bin/bash # chkconfig: 2345 80 90 # description: auto_runnohup java -jar /usr/java/wbs-service.jar > /dev/null 2>&1 & echo $! > /var/run/wbs-service.pid3.添加进入系…

小匠物联联合亚马逊云助力企业数智化出海

如何让家电企业出海产品数智化之路走上康庄大道&#xff1f;8月25日,亚马逊云科技[创新成长企业专列]这趟上云快车将开往宁波站&#xff0c;助力宁波的制造、软件等企业扬帆起航&#xff01;现场举办“亚马逊云科技助力企业出海数智沙龙”&#xff0c;小匠物联受邀出席。 会议现…