【Redis 系列】常用数据结构---ZSET类型

2025博客之星年度评选已开启 10w+人浏览 3.6k人参与


🚀 欢迎来到我的优快云博客:Optimistic _ chen
一名热爱技术与分享的全栈开发者,在这里记录成长,专注分享编程技术与实战经验,助力你的技术成长之路,与你共同进步!


🚀我的专栏推荐

专栏内容特色适合人群
🔥C语言从入门到精通系统讲解基础语法、指针、内存管理、项目实战零基础新手、考研党、复习
🔥Java基础语法系统解释了基础语法、类与对象、继承Java初学者
🔥Java核心技术面向对象、集合框架、多线程、网络编程、新特性解析有一定语法基础的开发者
🔥Java EE 进阶实战Servlet、JSP、SpringBoot、MyBatis、项目案例拆解想快速入门Java Web开发的同学
🔥Java数据结构与算法图解数据结构、LeetCode刷题解析、大厂面试算法题面试备战、算法爱好者、计算机专业学生

🚀我的承诺:
✅ 文章配套代码:每篇技术文章都提供完整的可运行代码示例

✅ 持续更新:专栏内容定期更新,紧跟技术趋势

✅ 答疑交流:欢迎在文章评论区留言讨论,我会及时回复(支持互粉)


🚀 关注我,解锁更多技术干货!
⏳ 每天进步一点点,未来惊艳所有人!✍️ 持续更新中,记得⭐收藏关注⭐不迷路 ✨

📌 标签:#技术博客#编程学习#Java#C语言#算法#程序员

Zset(有序集合)

与String、list、hash、set相比,zset最显著的区别在于:它既保持了集合元素唯一性的特点,又通过为每个元素关联一个浮点型的score值实现了有序性这种排序不是基于元素下标,而是完全由score值决定。

在这里插入图片描述
还有一点,有序集合中元素是不允许重复的,但允许分数重复;类似于考试成绩,每一个人有唯一的分数,但分数可能有多个人。

普通命令

ZADD

作用:添加或者更新指定元素以及关联的分数到zset中,分数应该符合double类型,+inf/-inf作为分数正负极限

#语法
zadd key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...] 

解释相关选项:

  • XX:仅仅用于更新已经存在的元素,不会添加新元素
  • NX:仅用于添加新元素,不会更新已经存在的元素
  • CH:默认情况,zadd返回的是本次添加的元素个数,但指定这个选项之后,就还会包含本次更新的元素个数
  • INCR:此时命令类似类似ZINCRBY的效果,将元素的分数加上指定分数。此时只能指定⼀个元素和分数

在这里插入图片描述

时间复杂度:O(log(N))
返回值:本次添加成功的元素个数

ZCARD

作用:获取一个zset中的元素个数

zcard key

在这里插入图片描述

返回值:zset内的元素个数

ZCOUNT

作用:返回分数在min和max之间的元素个数,默认情况下,min和max都是包含的,否则可以通过 ( 排除

zcount key min max

在这里插入图片描述

时间复杂度:O(log(N))
返回值:满足条件的元素列表个数

ZRANGE 和 ZREVRANGE

ZRANGE:返回指定区间的元素,按照分数升序,WITHSCORES可以把分数也返回

zrange key start stop [withscores]
  • 此处的[start, stop] 为下标构成的区间.从0开始,⽀持负数

ZREVRANGE:返回指定区间里的元素,按照分数降序,WITHSCORES可以把分数也返回

zrevrange key start stop [withscores]

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dd162f4d88714bfcb755bb16c6caf743.pn

时间复杂度:O(Log(N)+M)
返回值:区间内的元素列表

ZPOPMAX 和 ZPOPMIN

ZPOPMAX :删除并返回分数最高的count个元素

zpopmax key [count]

ZPOPMIN :删除并返回分数最低的count个元素

zpopmin key [count]

在这里插入图片描述

时间复杂度:O(log(N)*M)
返回值:分数和元素列表

ZRANK 和 ZREVRANK

ZRANK :返回指定元素的排名,升序(索引从0开始)

zrank key member

ZREVRANK:返回指定元素的排名,降序(索引从0开始)

zrevrank key member

在这里插入图片描述

时间复杂度:O(Log(N))
返回值:排名

ZSCORE

作用:返回指定元素的分数

zscore key member

在这里插入图片描述

返回值:分数

ZREM

作用:删除指定元素

zrem key member [member ...]

在这里插入图片描述

时间复杂度:O(log(N)*M)
返回值:本次操作删除的元素个数

zremrangebyrank 和 zremrangebyscore

zremrangebyrank :按照排序,升序删除指定范围的元素,左闭右闭

zremrangebyrank key start stop

在这里插入图片描述


zremrangebyscore:按照分数,删除指定范围的元素,左闭右闭

zremrangebscore key min max

在这里插入图片描述

时间复杂度:O(log(N)+M)
返回值:本次操作删除的元素个数。

ZINCRBY

作用:为指定元素的关联分数添加指定的分数值

zincrby key increment member

在这里插入图片描述

时间复杂度:O(log(N))
返回值:增加后元素的分数

集合之间的操作

zinterstore(交集)

作用:得到有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的逻辑得到新的分数

zinterstore destination numkeys key [key ...] 
      [weights weight [weight ...]]
      [aggregate <sum | MIN | MAX>]
  • destination存储结果的目标键名
  • numkeys指定后面键(集合)的数量
  • key参与交集计算的有序集合键
  • weights权重选项,在计算交集时,对每个输入有序集合的分数进行加权;权重对应规则,weights 按顺序应用于 key
  • aggregate当成员在多个集合中存在时,如何计算最终分数(取和 | 取最小值 | 取最大值)

在这里插入图片描述

时间复杂度:O(NK)+O(Mlog(M)) N是输⼊的有序集合中,最⼩的有序集合的元素个数;K是输⼊了⼏个有序集合;M是最终结果的有序集合的元素个数.
返回值:⽬标集合中的元素个数

zunionstore(并集)

作用:得到给定有序集合中元素的并集并保存到目标有序集合中,在合并过程中以元素为单位合并,元素对应的分数按照不同的逻辑得到新的分数

zunionstore destination numkeys key [key ...] [weights weight [weight...]] [aggregate <sum | min | max>]

在这里插入图片描述

时间复杂度:O(N)+O(M*log(M)) N是输⼊的有序集合总的元素个数;M是最终结果的有序集合的元素个数
返回值:目标集合中的元素个数

内部编码

Redis 的 ZSet(有序集合)是一个功能强大的数据结构,它内部为了在内存使用和操作效率之间取得平衡,根据特定条件,会使用两种不同的内部编码(数据结构):listpack (ziplist在redis7.0+被listpack完全代替)和 skiplist(更准确地说是 skiplist + dict 的组合)

listpack(列表包)

listpackziplist的改进版本,提供与 ziplist 相似的内存效率,ziplist 中每个条目都存储了前一个条目的长度,当更新一个条目时,可能需要更新后面所有条目(连锁更新)。listpack 将长度信息放在每个条目内部,消除了连锁更新。

  • 最多允许的元素个数:zset-max-listpack-entries:128
  • 字符串的最大字节长度:zset-max-listpack-value:64

使用listpack编码时,ZSet所有元素按照score的值从小到大的顺序,紧密排列在一个listpack中,所以查找元素时需要遍历查找。

在这里插入图片描述

Skiplist+Dict(跳跃表+字典)

前面很多的命令的时间复杂度都是较为复杂,原因就在这个编码方式中。

  • Skiplist(跳跃表):按分数排序,支持O(logN)的范围查找,排序。
  • Dict(字典):实现成员到分数的O(1)映射

特点:

  • 数据共享:两个结构共享同一份成员和分值数据
  • 操作高效:所有操作都在 O(1) 到 O(logN) 之间
  • 内存开销:Skiplist每个节点有多层指针(空间换时间)
  • 代码复杂度高,维护两个数据结构一致

SCAN渐进式遍历

作用: 使⽤scan命令进⾏渐进式遍历键,进⽽解决直接使⽤keys获取键时可能出现的阻塞问题。每次scan命令的时间复杂度是O(1),但是要完整地完成所有键的遍历,需要执⾏多次scan即可。

scan cursor [match pattern] [COUNT count] [TYPE type]
  • cursor(游标)迭代的起始位置;首次调用从0开始迭代;后续使用上一次返回的游标值;返回0表示遍历完成
  • match pattern(模式匹配)使用 glob 风格模式过滤键
  • COUNT count(返回数量)建议每次迭代返回的键数量,默认10
  • TYPE type(类型过滤)按照类型过滤,只返回某类型键

返回值:下一次游标(cursor)以及本次得到的键

在这里插入图片描述
注意:选择某个选项后,之后每次SCAN都必须明确指定筛选条件。
在这里插入图片描述

官方文档只是强调:只要将上一次调用返回的游标用于下一次调用,即可延续迭代。对于MATCH、COUNT、TYPE等选项。但没有明文规定这些选项是否必须随游标传递。

但是我们实际操作后发现,这类筛选参数不会被自动记住,每次调用都必须显式提供才能维持筛选条件。

完结撒花!🎉

如果这篇博客对你有帮助,不妨点个赞支持一下吧!👍
你的鼓励是我创作的最大动力~

想获取更多干货? 欢迎关注我的专栏 → optimistic_chen
📌 收藏本文,下次需要时不迷路!

我们下期再见!💫 持续更新中……


悄悄说:点击主页有更多精彩内容哦~ 😊

在这里插入图片描述

<think>嗯,用户现在问的是RedisZset数据结构。我需要先回想一下Zset的底层实现,可能涉及到跳跃列表(Skip List)和压缩列表(ziplist)或者后来的listpack,还有dict。 首先,用户可能已经知道Zset是有序集合,每个元素都有一个分数,可以按分数排序。但底层结构呢?可能需要解释两种编码方式:ziplist和skiplist+dict的组合。不过Redis的版本不同,可能ziplist被listpack替代了?比如在7.0之后,ziplist被替换为listpack,但用户可能还在用旧版本,或者资料可能有差异,需要确认。 然后,用户可能想知道为什么Zset要用这两种结构。比如,跳跃列表支持快速的插入、删除和范围查询,而哈希表(dict)提供O(1)的单元素查询。需要解释两者的结合如何优化性能。 另外,用户可能不太明白跳跃列表的结构和工作原理。需要简单说明跳跃列表的层级,如何加速查询,以及为什么比普通链表更高效。可能需要用例子说明,比如元素1到5,跳跃列表如何建立多级索引。 还要提到压缩列表(或listpack)在元素较少时的使用,这时候内存效率更高。而当元素数量或大小超过阈值时,会自动转换为跳跃列表和字典的组合。这样的设计在空间和性能之间取得平衡。 用户的应用场景可能包括排行榜、延迟队列等,需要举例说明Zset在这些场景中的实际应用,比如按分数排序的排行榜,定时任务的处理。这可以帮助用户理解为什么选择Zset而不是其他数据结构。 可能用户的问题背后是想了解如何高效使用Zset,或者遇到性能问题,比如插入慢或查询慢,这时候了解底层结构有助于优化。比如,当元素很多时,使用跳跃列表更高效,而小数据时用压缩列表节省内存。 需要检查是否有遗漏的重要点,比如Zset常用命令,如ZADD、ZRANGE等,但用户的问题主要是数据结构,可能不需要详细讲命令,但可以简要提及命令如何与底层结构互动。 最后,要确保解释清晰,结构分明,分点说明,可能用例子来辅助理解,比如插入元素时如何更新跳跃列表和字典,范围查询如何利用跳跃列表快速定位。</think>### **Redis Zset 数据结构详解** Redis 的 **Zset(有序集合)** 是一种结合了“集合唯一性”和“有序性”的数据结构,每个元素关联一个分数(score),通过分数排序。其核心设计目标是 **高效支持范围查询和单元素操作**,底层通过两种数据结构组合实现: 1. **跳跃列表(Skip List)**:用于快速范围查询和有序遍历。 2. **哈希表(Dict)**:用于 O(1) 时间复杂度查找单个元素对应的分数。 > *注:Redis 7.0 之前使用 `ziplist`(压缩列表)存储少量元素以节省内存,7.0 后改为 `listpack`。当元素数量或大小超过阈值时,自动转换为 `skiplist + dict` 组合。* --- #### **1. 跳跃列表(Skip List)** **作用**:实现有序元素的快速插入、删除和范围查询(如 `ZRANGE`)。 **结构特点**: - **多层链表**:每个节点随机生成多层指针(层数由概率决定,如每层有 50% 概率升级)。 - **分层索引**:高层指针跨越多个元素,加速查找(类似二分查找)。 **示例**: 假设 Zset 包含元素:`{A:1, B:2, C:3, D:4, E:5}`,跳跃列表结构可能如下(简化): ``` 层3: A ---------------------------> E 层2: A --------> C --------> E 层1: A -> B -> C -> D -> E ``` - **查询 `D:4`**:从最高层(层3)开始,发现 `A→E` 跨度太大,降层到层2,发现 `C→E`,再降层到层1找到 `D`。 **优势**: - 平均时间复杂度:插入、删除、查询均为 $O(\log n)$。 - 范围查询(如 `ZRANGEBYSCORE`)效率远高于平衡树(无需中序遍历)。 --- #### **2. 哈希表(Dict)** **作用**:存储元素(member)到分数(score)的映射,支持 O(1) 时间复杂度查找单个元素的分数。 **结构特点**: - 键:元素值(如 `"user:1001"`)。 - 值:分数(如 `150`)。 **示例**: ``` Dict 内容: "user:1001" → 150 "user:1002" → 200 ``` - **执行 `ZSCORE user:1001`**:直接通过哈希表获取分数 150。 --- #### **3. 两种编码方式的切换** - **`listpack`(或旧版 `ziplist`)**: - **适用场景**:元素数量少(默认 ≤128)且元素值长度小(默认 ≤64 字节)。 - **存储方式**:元素和分数按 `[member1, score1, member2, score2...]` 顺序紧凑排列。 - **`skiplist + dict`**: - **触发条件**:元素数量或大小超过阈值。 - **优势**:牺牲部分内存,换取高效的范围查询和单元素操作。 --- #### **4. 操作示例与底层行为** 以命令 `ZADD rankings 150 "user:1001"` 为例: 1. 若当前使用 `listpack` 编码: - 将 `"user:1001"` 和 `150` 追加到 `listpack` 尾部。 - 若插入后元素数量超过阈值,转换为 `skiplist + dict`。 2. 若使用 `skiplist + dict`: - **Dict**:插入键 `"user:1001"`,值 `150`。 - **Skip List**:按分数 150 插入新节点,并随机生成层数。 --- #### **5. 核心优势与应用场景** | **场景** | **Zset 的作用** | **底层支持** | |----------------|----------------------------------------|---------------------------| | 排行榜 | 按分数排序(如游戏积分) | Skip List 的范围查询 | | 延迟队列 | 按到期时间(score)排序,定时弹出任务 | `ZRANGEBYSCORE` 命令 | | 实时热度统计 | 更新元素分数并快速获取 Top N | `ZINCRBY` + `ZREVRANGE` | --- #### **6. 性能对比与总结** | **数据结构** | **插入/删除** | **单元素查询** | **范围查询** | **内存占用** | |------------------|-------------|--------------|-------------|-------------| | 跳跃列表 + 哈希表 | $O(\log n)$ | $O(1)$ | $O(\log n)$ | 较高 | | 压缩列表/listpack | $O(n)$ | $O(n)$ | $O(n)$ | 极低(小数据) | **设计思想**: - **空间换时间**:通过冗余存储(哈希表 + 跳跃列表)提升高频操作性能。 - **自适应编码**:根据数据规模动态选择内存与性能的平衡点。
评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Optimistic _ chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值