当前位置: 首页 > article >正文

Redis存储⑥Redis五大数据类型之 Zset

目录

 1. Zset 有序集合

1.1 Zset 有序集合常见命令

zadd

zcard

zcount

zrange

zrevrange

zrangebyscore(弃用)

zpopmax

bzpopmax

zpopmin

bzpopmin

zrank

zrevrank

zscore

zrem

zremrangebyrank

zremrangebyscore

zincrby

1.2 Zset有序集合间操作

zinterstore

zunionstore

1.3 Zset命令小结和内部编码

1.4 Zset有序集合使用场景

本篇完。


 1. Zset 有序集合

        有序集合相对于字符串、列表、哈希、集合来说会有一些陌生。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有一个唯一的浮点类型的分数(score)与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。

Zset 的内部数据结构是跳表。

如下图所示,该有序集合显示了三国中的武将的武力。

有序集合:

        有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利用有序集合,可以帮助我们在实际开发中解决很多问题。

        有序集合中的元素是不能重复的,但分数允许重复。类比于一次考试之后,每个人一定有一个唯一的分数,但分数允许相同。

列表、集合、有序集合三者的异同点: 


1.1 Zset 有序集合常见命令

zadd

        添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。注意:负无穷大不是无穷小,负无穷大的绝对值和无穷大是一样的。

zadd的相关选项:

  • xx:仅仅用于更新已经存在的元素(member),不会添加新元素。

  • nx:仅用于添加新元素(member),不会更新已经存在的元素。

  • lt:仅当新分数小于当前分数时才更新现有元素,不会阻止添加新元素。

  • gt:仅当新分数大于当前分数时才更新现有元素,不会阻止添加新元素。

  • ch:默认情况下,zadd 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。

  • incr:此时命令类似 zincrby 的效果,将元素的分数加上指定的分数。此时只能指定一个元素和分数。

语法:zadd key [nx | xx] [gt | lt] [ch] [incr] score member [score member ...]

        member 和 score 称为是一个 "pair",类似于 C++ 里的 std::pair。不要把它们理解成 “键值对”(key - value pair),键值对中是有明确的 “角色区分”,一定是根据键 -> 值。而对于有序集合来说,既可以通过 member 找到对应的 score,也可以通过 score 找到匹配的 member。

命令有效版本:1.2.0 之后

时间复杂度:O(log(N))

返回值:返回新增成功的元素个数。

示例:

如果修改的分数影响到了之前的顺序,就会自动的移动元素位置,保持原有的升序顺序不变:


不存在才插入:


存在才插入:


返回更新元素的个数:


给元素加上指定分数:


zcard

获取一个 zset 的基数(cardinality),即 zset 中的元素个数。

语法:zcard key 

命令有效版本:1.2.0 之后

时间复杂度:O(1)

返回值:zset 内的元素个数。

示例:


zcount

返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,如果不想要边界值,可以通过在边界值前加上 '(' 来排除。

语法:zcount key min max

命令有效版本:2.0.0 之后

时间复杂度:O(log(N))。先根据 min 找到对应的元素,再根据 max 找到对应的元素,两次都是 O(log(N))。实际上,Zset 内部会记录每个元素当前的 “排行” / “次序”,查询到元素就直接知道了元素所在的 “次序”(下标),就可以直接把 max 对应的元素次序和 min 对应的元素次序做减法即可。

返回值:满足条件的元素列表个数。

示例:(注意最后括号的写法,与常见的不同)


zrange

返回指定区间里的元素,分数按照升序。带上withscores可以把分数也返回。

语法:zrange key start stop [withscores]  。此处的 [start, stop] 为下标构成的区间,从 0 开始,支持负数。

命令有效版本:1.2.0 之后

时间复杂度:O(log(N)+M),N 是整个有序集合的元素个数,M 是 start - stop 区间内的元素个数。

返回值:区间内的元素列表。

示例:

        Redis 内部存储数据是按照二进制的方式存储的,意味着 Redis 服务器是不负责字符编码的,所以要把二进制对回到汉字需要客户端支持:


zrevrange

返回指定区间里的元素,分数按照降序。带上 withscores可以把分数也返回。

备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。

语法:zrevrangekey start stop [withscores] 

命令有效版本:1.2.0 之后

时间复杂度:O(log(N)+M)

返回值:区间内的元素列表。

示例:


zrangebyscore(弃用)

返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 '(' 排除。

备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。

语法:zrangebyscore key min max [withscore]  

命令有效版本:1.0.5 之后

时间复杂度:O(log(N)+M)

返回值:区间内的元素列表。

示例:


zpopmax

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

语法:zpopmax key [count]

命令有效版本:5.0.0 之后

时间复杂度:O(log(N) * M),N 是有序集合的元素个数,M 表示 count,要删除的元素个数。

        既然是尾删,为什么不把最后一个元素的位置特殊标记一下,后续删除不久省却了查找过程,直接 O(1) 了吗?

        这个是有可能的,但是目前 Redis 并没有这么做。事实上,Redis 的源码中针对有序集合确实是记录了尾部的特定位置,但是在实际删除的时候并没有用上这个特性,而是直接调用了一个 “通用的删除函数”(给定一个 member 的值,进行查找,找到位置之后再删除)。(一些人认为此处是存在优化空间的)

返回值:分数和元素列表。

示例:

        如果存在多个元素分数相同(分数是主要因素,相同的情况下会按照 member 字符串的字典序来决定先后顺序),同时为最大值,那么 zpopmax 删除最大元素时,仍然只会删除其中一个元素。


bzpopmax

zpopmax 的阻塞版本。可以同时读多个有序集合。

语法:bzpopmax key [key ...] timeout。timeout 单位是 s,支持小数形式。

命令有效版本:

5.0.0 之后

时间复杂度:O(log(N)),删除最大值花费的时间。如果当前 BZPOPMAX 同时监听多个 key,假设 key 是 M 个,那么此时时间复杂度是 O(log(N) * M) 吗?每个这样的 key 上面都删除一次元素才需要 * M,而这里是从这若干个 key 中只删除一次。

返回值:元素列表。

示例:


zpopmin

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

语法:zpopmin key [count] 

命令有效版本:5.0.0 之后

时间复杂度:O(log(N) * M)

返回值:分数和元素列表。

示例:


bzpopmin

zpopmin 的阻塞版本。

语法:bzpopmin key [key ...] timeout

命令有效版本:5.0.0 之后

时间复杂度:O(log(N))

返回值:元素列表。

和bzpopmax阻塞效果类似,不做演示。


zrank

返回指定元素的排名,升序。

语法:zrank key member

命令有效版本:2.0.0 之后

时间复杂度:O(log(N))。zrank 查找元素的过程和 zcount是一样的。

返回值:排名。

示例:


zrevrank

返回指定元素的排名,降序

语法:zrevrank key member 

命令有效版本:2.0.0 之后

时间复杂度:O(log(N))

返回值:排名。

示例:


zscore

返回指定元素的分数。

语法:zscore key member 

命令有效版本:1.2.0 之后

时间复杂度:O(1)。此处相当于 Redis 对于这样的查询操作做了特殊优化,付出了额外的空间代价。

返回值:分数。

示例:


zrem

删除指定的元素。

语法:zrem key member [member ...] 

命令有效版本:1.2.0 之后

时间复杂度:O(M*log(N))

返回值:本次操作删除的元素个数。

示例:


zremrangebyrank

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

语法:zremrangebyrank key start stop  

命令有效版本:2.0.0 之后

时间复杂度:O(log(N)+M)

返回值:本次操作删除的元素个数。

示例:


zremrangebyscore

按照分数删除指定范围的元素,左闭右闭,也可以使用 '(' 来排除边界值。

语法:zremrangebyscore key min max 

命令有效版本:1.2.0 之后

时间复杂度:O(log(N)+M)

返回值:本次操作删除的元素个数。

示例:


zincrby

为指定的元素的关联分数添加指定的分数值。

语法:zincrby key increment member

命令有效版本:1.2.0 之后

时间复杂度:O(log(N))

返回值:增加后元素的分数。

示例:

zincrby 不光会修改分数内容,也能同时移动元素位置,保证整个有序集合仍然是升序的。


1.2 Zset有序集合间操作

有序集合的交集操作:


zinterstore

        求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

        在有序集合中,member 是元素的本体,score 只是辅助排序的工具人。因此,在进行比较 “相同” 时,只要 member 相同即可。如果 member 相同,score 不同,进行交集合并之后的最终分数看 AGGREGATE 后面的属性。

语法:zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate <sum| min | max>]

numkeys 是一个整数,用来描述后续有几个 key 参与交集运算。

命令有效版本:2.0.0 之后

时间复杂度:O(N*K)+O(M*log(M)),N 是输入的有序集合中,最小的有序集合的元素个数;K 是输入了几个有序集合;M 是最终结果的有序集合的元素个数。

返回值:目标集合中的元素个数。

示例:


zunionstore

        求出给定有序集合中元素的并集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

有序集合的并集操作:

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

命令有效版本:2.0.0 之后

时间复杂度:O(N)+O(M*log(M)) N 是输入的有序集合总的元素个数,M 是最终结果的有序集合的元素个数。

返回值:目标集合中的元素个数

示例:


1.3 Zset命令小结和内部编码

有序集合命令:


Zset有序集合类型的内部编码有两种:

  • ziplist(压缩列表):当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用 ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
  • skiplist(跳表):当 ziplist 条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降。

简单来说,跳表是一个 “复杂链表”,查询元素的时间复杂度是 O(logN)。相比于树形结构,更适合按照范围获取元素。

1. 当元素个数较少且每个元素较小时,内部编码为 ziplist

2. 当元素个数超过 128 个,内部编码 skiplist

3. 当某个元素大于 64 字节时,内部编码 skiplist


1.4 Zset有序集合使用场景

        有序集合比较典型的使用场景就是排行榜系统。例如常见的网站上的热榜信息,榜单的维度可能是多方面的:按照时间、按照阅读量、按照点赞量。本例中我们使用点赞数这个维度,维护每天的热榜:

1. 添加用户赞数

例如用户 james 发布了一篇文章,并获得 3 个赞,可以使用有序集合的 zadd 和 zincrby 功能:

zadd user:ranking:2022-03-15 3 james

之后如果再获得赞,可以使用 zincrby:

zincrby user:ranking:2022-03-15 1 james

2. 取消用户赞数

        由于各种原因(例如用户注销、用户作弊等)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用 zrem。例如删除成员 tom:

zrem user:ranking:2022-03-15 tom

3. 展示获取赞数最多的 10 个用户

此功能使用 zrevrange 命令实现:

zrevrangebyrank user:ranking:2022-03-15 0 9

4. 展示用户信息以及用户分数

        此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用 zscore 和 zrank 来实现。

hgetall user:info:tom
zscore user:ranking:2022-03-15 mike
zrank user:ranking:2022-03-15 mike

本篇完。

下一篇是Redis存储⑦其它数据类型+渐进式遍历+数据库管理


http://www.kler.cn/a/542390.html

相关文章:

  • 计算机毕业设计——Springboot的旅游管理
  • Anything llm Token数理解
  • A*寻路详解
  • data attributes
  • Unity 打造游戏资源加密解密系统详解
  • 方舟字节码原理剖析:架构、特性与实践应用
  • MFC线程安全案例
  • 对JVM的错误理解与纠正
  • 解决虚幻Unreal Engine手动配置安卓打包环境后无法识别SDK问题
  • Express 中间件
  • VUE3项目结构说明
  • android studio开发科大讯飞最新版
  • 深入理解x86汇编:GNU格式的全面指南
  • 深入理解Java对接DeepSeek
  • 如何在WPF中实现软件内嵌效果
  • Baklib使数字化内容管理更加高效和便捷设计理念和实用案例解析
  • Linux 系统使用教程
  • 使用epoll与sqlite3进行注册登录
  • 基于ESP32的远程开关灯控制(ESP32+舵机+Android+物联网云平台)——下
  • linux部署nacos集群
  • 【Elasticsearch】derivative聚合
  • CSS 属性选择器详解与实战示例
  • 认识Electron 开启新的探索世界一
  • 香港服务器远程超出最大连接数的解决方案是什么?
  • android 指定跳转页面返回触发
  • 数据分析:蛋白质组的GO term的富集分析详解