Redis Zset 类型:Score 属性在数据排序中的作用

Zset 有序集合

    • 一 . zset 的引入
    • 二 . 常见命令
      • 2.1 zadd、zrange
      • 2.2 zcard
      • 2.3 zcount
      • 2.4 zrevrange、zrangebyscore
      • 2.5 zpopmax、zpopmin
      • 2.6 bzpopmax、bzpopmin
      • 2.7 zrank、zrevrank
      • 2.8 zscore
      • 2.9 zrem、zremrangebyrank、zremrangebyscore
      • 2.10 zincrby
      • 2.11 集合间操作
        • 交集 : zinterstore
        • 并集 : zunionstore
      • 小结
    • 三 . 内部编码
    • 四 . 应用场景 : 排行榜

Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家讲解的是 Redis 中 Zset 类型的相关内容 , 它在 set 类型的基础上又引入了 score 属性 , 那我们可以一起来看看 score 具体起到了什么作用 .

在这里插入图片描述

本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践


一 . zset 的引入

我们之前学习过 set 类型 . 他有两个显著的特点

  1. 唯一 : set 中的元素不能重复
  2. 无序 : set 中的元素是无序的 , 比如 : [1 , 2 , 3] 和 [3 , 2 , 1] 是同一个集合

而 list 是要求有序的 , [1 , 2 , 3] 和 [3 , 2 , 1] 不是同一个列表
那大家要注意一点 : 我们的有序集合 (Zset) 中的有序和列表 (list) 中的有序不是一个有序 .

  • Zset 中的有序指的是升序 / 降序
  • list 中的有序指的是顺序固定

那 Zset 既然是有序的 , 那它所排序的规则是什么 ?
为了方便描述排序规则 , 我们给 Zset 的 member 中同时引入了一个属性 : score (分数) , 他是浮点类型的 . 每个 member 都会分配一个分数 . 进行排序就按照此处的分数大小来进行升序 / 降序排序 .
image.png
我们从图片里面也可以看到 , 在 value 中给每个元素都分配了一个分数属性 .

那 Zset 也是 set , 也要求元素是不能重复的 , 但是分数是可以重复的 .

Zset 主要还是用来存储元素的 , 分数属性只是起到了一个辅助的作用

那我们再来对比一下 list、set、Zset 的具体区别

数据结构是否允许重复元素是否有序有序依据应用场景
列表索引下标消息队列等
集合用户标签等
有序集合分数属性排行榜系统等

二 . 常见命令

2.1 zadd、zrange

zadd 的作用是向有序集合中添加元素以及分数属性
语法 : zadd key score1 member1 [score2 member2]

在添加的时候 , 既需要添加元素 , 又需要添加分数属性

如果当前的 member 并不存在 , 此时就会达到添加新 member 的效果 . 如果 member 已经存在 , 此时就会更新分数
时间复杂度 : O(logN) , N 指的是有序集合中的元素个数

由于 Zset 是有序结构 , 要求新增的元素就必须要放到合适的位置上

返回值代表新增成功元素的个数

如果产生了修改操作 , 那返回值是返回 0 而不是 1 , 因为修改操作并不算新增元素

如果多个元素有相同的分数 , 那么相同的元素之间就按照字典序进行排列
image.png
如果多个元素分数不同 , 那么这些元素之间依然以 score 来进行升序排列 .

Zset 的默认排序方式是升序的 .

那 zadd 还有一系列的额外属性可以设置
zadd key [nx | xx] [gt | lt] [ch] [incr] score1 member1 [score2 member2]
我们分别来看 :

  1. [nx | xx] : nx 表示不存在才去设置 , 而 xx 指的是存在才去设置 (相当于更新操作)

我们的 nx / xx 实际上并不是针对 key 来去判断的 , 而是根据 member 来去判断的 .
如果 member 不存在 , nx 就会设置成功 , 但是如果 member 存在 , nx就会设置失败 .
如果 member 存在 , xx 就会设置成功 , 但是如果 member 不存在 , xx 就会设置失败 .

  1. [gt | lt] : 如果要插入的元素不存在 , 正常插入 ; 如果要插入的元素存在 , 那就需要判断下面两条规则
    1. gt (greater than) : 大于 , 如果新的分数 > 当前分数 , 更新才会成功
    2. lt (less than) : 小于 , 如果新的分数 < 当前分数 , 更新才会成功

也就是说我们现在要更新分数属性了 , lt 的规则是如果给定的新的分数比之前的分数小 , 就会更新成功 . gt 的规则是如果给定的新的分数比之前的分数大 , 就会更新成功

  1. [ch] : 描述了返回值要返回什么信息 . 本来 zadd 的返回值为新增成功的元素个数 , 如果我们添加了 ch , 也会返回集合中被修改的元素个数
  2. [incr] : 对集合中的分数属性进行运算 , 比如 : 加减分数等等

如果我们想要查看有序集合中的元素 , 使用 zrange 这个命令即可
语法 : zrange key start stop [withscores]

zrange key 0 -1 就是查看有序列表中所有的元素
[withscores] 的作用是也打印出元素对应的分数属性

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

O(log(N)) 是根据下标找到边界值
O(M) 是需要遍历每个元素他的成绩属性 , 也就是 stop~end 之间的元素个数

接下来我们通过一些具体操作来理解 zadd 和 zrange 这两个命令
先来看一下 zadd 的基本操作

127.0.0.1:6379> zadd key 99 甄嬛 88 沈眉庄 77 安陵容 # 向有序集合中添加元素
(integer) 3
127.0.0.1:6379> zrange key 0 -1 # 获取有序集合中所有的元素
1) "\xe5\xae\x89\xe9\x99\xb5\xe5\xae\xb9"
2) "\xe6\xb2\x88\xe7\x9c\x89\xe5\xba\x84"
3) "\xe7\x94\x84\xe5\xac\x9b"

那为什么产生乱码了呢 ?
这是因为 Redis 中存储数据是按照二进制的方式来去存储的 , 它并不负责字符的编码与解码的 , 也就是说存进去 Redis 的数据是什么 , 那取出来就是他的二进制结果
如果我们想打印中文 , 我们就需要交给 Redis 的客户端来去操作 , 退出 Redis 客户端 (Ctrl C) , 然后通过 redis-cli --raw 来去启动客户端

root@hecs-327683:~# redis-cli --raw # 利用 Redis 的客户端来去打印中文
127.0.0.1:6379> zrange key 0 -1 # 获取有序集合中的元素
安陵容
沈眉庄
甄嬛
127.0.0.1:6379> zrange key 0 -1 withscores # 打印元素以及他的成绩属性
# 默认是升序存放
安陵容
77
沈眉庄
88
甄嬛
99

那我们再模拟一下修改成绩的情况呢 ?
我们刚才也介绍了 , zadd 实际上就是如果没有 member 那就新增 , 如果 member 存在 , 那就是覆盖操作

127.0.0.1:6379> zadd key 90 安陵容 # 修改安陵容的成绩属性
0 # 0 表示没有添加新的元素
127.0.0.1:6379> zrange key 0 -1 withscores 
沈眉庄
88
# 此时修改安陵容的成绩属性 , 就导致了之前的顺序改变了
# 那 Redis 就会自动的移动元素的位置 , 保证升序排列
安陵容
90
甄嬛
99

接下来 , 我们来去模拟 zadd 的各种属性的操作
(1) nx : 只能添加新元素 , 不会影响已经存在的元素
(2) xx : 只能更新已有的元素 , 不会添加新元素

127.0.0.1:6379> zadd key nx 93 皇后 # 利用 nx 添加一条不存在的元素
1 # 返回值为 1 代表新增成功一条元素
127.0.0.1:6379> zrange key 0 -1 withscores
沈眉庄
88
安陵容
90
# 新增的元素也按照正确的顺序插入进来了
皇后
93
甄嬛
99
127.0.0.1:6379> zadd key nx 50 皇后 # nx 对已经存在的元素不会产生任何影响
0 # 返回值为 0 代表没有新增元素
127.0.0.1:6379> zrange key 0 -1 withscores
沈眉庄
88
安陵容
# 皇后的成绩修改失败 -> nx 只能插入不存在的数据
90
皇后
93
甄嬛
99
------
127.0.0.1:6379> zadd key xx 50 皇后 # 利用 xx 来去修改皇后的成绩
0 # 返回值为 0 代表没有添加元素
127.0.0.1:6379> zrange key 0 -1 withscores
# 此时皇后的成绩修改成功
皇后
50
沈眉庄
88
安陵容
90
甄嬛
99
127.0.0.1:6379> zadd key xx 88 端妃 # xx 不能插入新的数据
0 # 返回值为 0 代表没有添加元素
127.0.0.1:6379> zrange key 0 -1 withscores
# 此时端妃没插入进来 -> xx 只能进行更新操作
皇后
50
沈眉庄
88
安陵容
90
甄嬛
99

(3) 默认情况下 zadd 命令返回的是添加成功的元素个数 , 但是我们如果指定 ch 这个选项之后 , 返回值就还会包含本次更新的元素的个数

127.0.0.1:6379> zadd key ch 80 皇后 # 修改皇后的成绩 , 添加 ch 选项表示返回值也包括了更新元素的个数
1 # 成功更新一条数据
127.0.0.1:6379> zrange key 0 -1 withscores
# 此时皇后的数据也更新成功了
皇后
80
沈眉庄
88
安陵容
90
甄嬛
99

(4) incr 的作用是将元素的分数加上指定的分数 (就相当于更新操作) , 但是如果使用 incr , 那此时只能指定一个元素和一个分数

127.0.0.1:6379> zadd key incr 10 皇后 # 对皇后的成绩进行 +10
90 # 返回值代表添加之后的成绩
127.0.0.1:6379> zrange key 0 -1 withscores
沈眉庄
88
# 如果成绩相同 , 那就按照元素的字典序进行排列
安陵容
90
# 此时皇后的成绩添加成功
皇后
90
甄嬛
99

2.2 zcard

zcard 的作用是用来获取有序集合中的元素个数
语法 : zcard key
时间复杂度 : O(1)

127.0.0.1:6379> zrange key 0 -1 withscores
# 一共有四个元素
沈眉庄
88
安陵容
90
皇后
90
甄嬛
99
127.0.0.1:6379> zcard key # 获取有序集合中的元素个数
4

2.3 zcount

zcount 的作用是按照分数区间来进行筛选 , 也就是说筛选 [min , max] 之间符合的元素个数
语法 : zcount key min max

min 和 max 默认是闭区间 , 如果我们想排除边界值 , 我们就需要在 min 和 max 的左侧加上括号 (比较奇葩 , 大家仔细观察)

时间复杂度 : O(logN)

zcount 需要制定 min 和 max 分数区间的 , 那就需要先根据 min 找到对应的元素 , 再根据 max 找到对应的元素 , 这两个操作都是 logN 的操作 , 那在这两个元素之间所有的元素都是符合条件的元素

127.0.0.1:6379> zrange key 0 -1 withscores
沈眉庄
88
安陵容
90
皇后
90
甄嬛
99
# 反人类 !!!
127.0.0.1:6379> zcount key 90 99 # 查询分数在 [90,99] 之间的元素个数
3
127.0.0.1:6379> zcount key (90 99 # 查询分数在 (90,99] 之间的元素个数
1
127.0.0.1:6379> zcount key 90 (99 # 查询分数在 [90,99) 之间的元素个数
2
127.0.0.1:6379> zcount key (88 (99 # 查询分数在 (90,99) 之间的元素个数
2

那 min 和 max 也是可以写成浮点数的 , 那在浮点数中有存在两个特殊的数值

  1. inf : 无穷大
  2. -inf : 负无穷大

那在 Zset 中分数也是支持使用 inf 和 -inf 的

127.0.0.1:6379> zcount key -inf inf # 查询分数在 [-∞,+∞] 之间的元素个数
4

2.4 zrevrange、zrangebyscore

zrevrange 是按照成绩降序打印
语法 : zrevrange key start stop [withscores]
时间复杂度 : O(log(N) + M)

127.0.0.1:6379> zrevrange key 0 -1 withscores # 降序打印有序集合中的元素
# 此时元素都按照降序排列了
甄嬛
99
皇后
90
安陵容
90
沈眉庄
88

zrangebyscore 是按照成绩来去查找元素的 , 查找成绩在 [min , max] 区间内的元素
语法 : zrangebyscore key min max [withscores]

127.0.0.1:6379> zrangebyscore key 90 100 withscores # 查询成绩在 [90,100] 之间的元素
安陵容
90
皇后
90
甄嬛
99

但是这个命令比较遗憾 , 在 6.2.0 之后会被废弃 , 会被合并到 zrange 中去使用

2.5 zpopmax、zpopmin

zpopmax 的作用是删除并返回成绩最高的 count 个元素
语法 : zpopmax key [count]

其实可以理解为删除堆顶元素 , 也就是 pop() 方法

返回值就是被删除的元素 (member + score)
时间复杂度 : O(log(N) * M)

N 是有序集合的元素个数 , O(log(N)) 指的是我们需要先查找成绩最高的元素
M 是要删除的元素个数 , 意思就是删除几个元素就查找几次成绩最高的元素

127.0.0.1:6379> zpopmax key # 弹出成绩最高的元素
甄嬛
99
127.0.0.1:6379> zpopmax key 2 # 弹出两个成绩最高的元素
皇后
90
安陵容
90

如果存在多个元素 , 他们的分数相同并且同时为最大值 , 那 zpopmax 删除元素的时候 , 该怎么删除呢 ?
分数虽然是主要因素 , 但是如果分数相同就会按照 member 的字典序来决定弹出的先后顺序

127.0.0.1:6379> zadd key 99 甄嬛 99 皇后 88 沈眉庄 # 构造两组成绩相同的数据
2
127.0.0.1:6379> zrange key 0 -1 withscores
沈眉庄
88
# 甄嬛和皇后的成绩相同
甄嬛
99
皇后
99
127.0.0.1:6379> zpopmax key # 删除成绩最高的元素
# 删除的是皇后 , 这是因为皇后的字典序比甄嬛高
皇后
99

zpopmin 是删除并返回成绩最小的元素
语法 : zpopmin key [count]
时间复杂度 : O(log(N) * M)

N 是有序集合的元素个数 , O(log(N)) 指的是我们需要先查找成绩最高的元素
M 是要删除的元素个数 , 意思就是删除几个元素就查找几次成绩最高的元素

127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu # 向有序集合中添加元素
(integer) 3
127.0.0.1:6379> zrange key 0 -1 withscores # 查看有序集合中所有元素(默认升序排列)
1) "zhangsan"
2) "10"
3) "lisi"
4) "20"
5) "wangwu"
6) "30"
127.0.0.1:6379> zpopmin key # 删除并返回成绩最低的元素
1) "zhangsan"
2) "10"
127.0.0.1:6379> zpopmin key 2 # 删除并返回两个成绩最低的元素
1) "lisi"
2) "20"
3) "wangwu"
4) "30"

2.6 bzpopmax、bzpopmin

bzpopmax 就是阻塞版本的 zpopmax , 如果有序集合中已经有了元素 , 那就直接返回 ; 如果没有元素 , 就会产生阻塞
我们的有序集合也可以视为是一个优先级队列 , 那我们就可以实现一个带有阻塞功能的优先级队列
语法 : bzpopmax key1 [key2 …] timeout
其中 , 每个 key 都是一个有序集合 , timeout 表示超时时间 (最多阻塞多久) , 单位为 s .

timeout 可以设置小数形式 , 比如 : 0.1s 就是 100ms

它的特性也是在有序集合为空的时候开始阻塞 , 等到其他客户端插入数据就解除阻塞
时间复杂度 : O(logN)

O(logN) 需要先找到成绩最高的元素 , 然后删除

如果 bzpopmax 同时监听了多个 key , 那么他的时间复杂度依然是 O(logN) , 因为我们 bzpopmax 是删除成绩最高的元素 , 我们只需要删除这一个元素即可 , 不用乘 M

我们可以看一下演示
bzpopmax 阻塞场景.gif

那他执行的流程就是这个样子的
bzpopmax 阻塞模拟.png

其中 , 返回值包含了三部分信息

1) "key" # 表示是哪个有序集合添加了元素
# 成绩最高的元素
2) "wangwu"
3) "70"
(4.52s)

同理 , bzpopmin 的用法与 bzpopmax 用法相同
bzpopmin 阻塞场景.gif

2.7 zrank、zrevrank

zrank 的作用是返回指定元素的排名

所谓的排名 , 实际上就是下标

语法 : zrank key member
时间复杂度 : O(logN)

127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu # 向有序集合中添加元素
(integer) 3
127.0.0.1:6379> zrank key lisi # 返回 lisi 所在的下标
(integer) 1
127.0.0.1:6379> zrank key wangwu # 返回 wangwu 所在的下标
(integer) 2
127.0.0.1:6379> zrank key zhangsan # 返回 zhangsan 所在的下标
(integer) 0
127.0.0.1:6379> zrank key error # 元素不存在会返回 nil
(nil)

zrevrank 也是获取到 member 的下标 , 但是他是按照降序的顺序来获取下标 (也就是反着运算下标)

127.0.0.1:6379> zrevrank key zhangsan # 降序获取 zhangsan 的下标
(integer) 2
127.0.0.1:6379> zrevrank key wangwu # 降序获取 wangwu 的下标
(integer) 0

2.8 zscore

zscore 的作用是查询指定 member 的分数
时间复杂度 : O(1)

Redis 对 zscore 的查询做了特殊的优化 , 并不再是 O(logN) 了

127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu # 向有序集合中添加元素
(integer) 3
127.0.0.1:6379> zscore key zhangsan # 查询元素 zhangsan 的分数
"10"
127.0.0.1:6379> zscore key lisi # 查询元素 lisi 的分数
"20" 
127.0.0.1:6379> zscore key wangwu # 查询元素 wangwu 的分数
"30"

2.9 zrem、zremrangebyrank、zremrangebyscore

zrem 的作用是删除有序集合中指定的元素
语法 : zrem key member1 [member2 …]
时间复杂度 : O(log(N) * M)

N 指的是有序集合中的元素个数 , M 指的是要删除的元素个数
删除一个元素需要 O(logN) , 总共需要删除 M 个元素

127.0.0.1:6379> zrem key zhangsan # 删除元素 zhangsan
(integer) 1 # 返回值代表删除成功的元素个数
127.0.0.1:6379> zrem key lisi wangwu # 删除多个元素 lisi wangwu
(integer) 2

那 zremrangebyrank 的作用是按照排序结果升序删除指定下标范围的元素 , 也就是删除 [start , stop] 区间段的元素
语法 : zremrangebyrank key start stop
时间复杂度 : O(log(N) + M)

N 指的是有序集合的元素个数 , M 是 start~end 区间中的元素个数
思路就是我们先找到 start 位置的元素 , 时间复杂度为 O(logN) , 然后只需要往后再找 M 个元素即可 , 也就是 O(log(N) + M)

127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu 40 zhaoliu # 向有序集合中添加元素
(integer) 4
127.0.0.1:6379> zremrangebyrank key 1 2 # 删除下标在 [1,2] 之间的元素
(integer) 2
127.0.0.1:6379> zrange key 0 -1 withscores
# 下标为 1 的 lisi 和下标为 2 的 wangwu 就被删除掉了
1) "zhangsan"
2) "10"
3) "zhaoliu"
4) "40"

zremrangebyscore 是按照成绩来去删除元素 , 在 [min , max] 区间的成绩所对应的元素就会被删除
语法 : zremrangebyscore key min max

同样可以使用 ( 来去排除边界值的

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

N 指的是有序集合中的元素个数 , M 指的是要删除的区间内的元素个数
删除一个元素需要 O(logN) , 总共需要删除 M 个元素

127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu 40 zhaoliu # 向有序集合中添加元素
(integer) 4
127.0.0.1:6379> zremrangebyscore key 20 30 # 删除成绩在 [20,30] 区间范围的元素
(integer) 2
127.0.0.1:6379> zrange key 0 -1
# 成绩为 20 的 lisi 和成绩为 30 的 wangwu 就被删除了
1) "zhangsan"
2) "zhaoliu"

2.10 zincrby

zincrby 为指定的元素的分数进行修改
语法 : zincrby key increment member

127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu 40 zhaoliu # 向有序集合中添加元素
(integer) 4
127.0.0.1:6379> zincrby key 15 zhangsan # 对 zhangsan 的成绩 + 15 分
"25"
127.0.0.1:6379> zrange key 0 -1 withscores
1) "lisi"
2) "20"
# 此时有序集合的顺序也随之改变了
3) "zhangsan"
4) "25"
5) "wangwu"
6) "30"
7) "zhaoliu"
8) "40"
127.0.0.1:6379> zincrby key -15 zhangsan # 对 zhangsan 的成绩 - 15 分
"10"
127.0.0.1:6379> zrange key 0 -1 withscores
# 此时有序集合的顺序也随之改变了
1) "zhangsan"
2) "10"
3) "lisi"
4) "20"
5) "wangwu"
6) "30"
7) "zhaoliu"
8) "40"
127.0.0.1:6379> zincrby key 0.5 zhangsan # zincrby 也可以加减小数
"10.5"
127.0.0.1:6379> zrange key 0 -1 withscores
# zhangsan 的成绩已经 + 0.5 了
1) "zhangsan"
2) "10.5"
3) "lisi"
4) "20"
5) "wangwu"
6) "30"
7) "zhaoliu"
8) "40"

2.11 集合间操作

我们之前学习过 set 类型 , 他也存在集合间操作 , 分别为 : sinter、sunion、sdiff
那相对应的命令就是 zinter、zunion、zdiff ?
并不是 , 这三个命令在 Redis 6.2 之后才引进 , 我们使用的是 Redis 5 系列 , 先不去了解这几个命令
在 Zset 中提供了两个集合间操作

  1. zinterstore : 求交集 , 结果保存到另外一个集合中
  2. zunionstore : 求并集 , 结果保存到另外一个集合中
交集 : zinterstore

基本语法 : zinterstore destination numkeys key1 [key2 …]

  • destination 表示要把结果存储到 destination 中
  • numkeys 描述了后续有几个 key 参与交集运算 .

那为什么这里还需要单独描述出几个 key 参与运算呢 ?
主要是因为真正的语法中 , 后续还有很多选项 . 我们指定出 numkeys 描述出 key 的个数之后 , 就可以明确的知道后面的选项是从哪里开始的了
(我们可以类比 HTTP 的协议报文理解 , 通过 Content-Length 来描述正文的长度 , 如果 Content-Length 错误就容易产生粘包问题)

真正的语法 : zinterstore destination numkeys key1 [key2 …] [weights weight1 [weight2 …]] [aggregate <sum | min | max>]

  1. [weights weight1 [weight2 …]] : 指的是权重 (优先级) , 我们的有序集合是带有分数的 , 那每个集合他们的权重也是不同的 , 我们求交集既需要考虑相同的元素 , 又需要考虑对应集合的权重 . 权重和元素都相同才算是交集
  2. [aggregate <sum | min | max>] :

比如 :
key1 :

zhangsan10
lisi20
wangwu30

key2 :

zhangsan15
lisi25
zhaoliu30

在有序集合中 , member 才是主体 , score 只是用来辅助排序的 , 因此在进行比较相同的时候 , 只要 member 相同即可 , 不要求 score 相同
所以最终的交集就是 : zhangsan、lisi
如果 member 相同 , score 不同 , 那最后的 score 怎么算 ?
这就需要我们的 [aggregate <sum | min | max>] 来运算了 , 以 zhangsan 为例

  • sum : 求和 , 最终的 score 为 10 + 15 = 25
  • min : 取最小值 , 最终的 score 为 min(10,25) = 10
  • max : 求最大值 , 最终的 score 为 max(10,25) = 25

那我们通过命令来去理解一下
先来看最基础的版本

# 此时 , zhangsan 和 lisi 为交集结果
# 元素相同即可 , 不要求成绩相同
127.0.0.1:6379> zadd key1 10 zhangsan 20 lisi 30 wangwu
(integer) 3
127.0.0.1:6379> zadd key2 15 zhangsan 25 lisi 35 zhaoliu
(integer) 3
127.0.0.1:6379> zinterstore key3 2 key1 key2 # 获取 key1 和 key2 中的交集结果保存到 key3 中
(integer) 2 # 返回值代表交集元素的个数
127.0.0.1:6379> zrange key3 0 -1 withscores
1) "zhangsan"
2) "25" # 默认的就是将交集的 score 相加
3) "lisi"
4) "45"

然后我们模拟一下权重的效果

# 此时 , zhangsan 和 lisi 为交集结果
# 元素相同即可 , 不要求成绩相同
127.0.0.1:6379> zadd key1 10 zhangsan 20 lisi 30 wangwu
(integer) 3
127.0.0.1:6379> zadd key2 15 zhangsan 25 lisi 35 zhaoliu
(integer) 3# 将 key1 的权重设置为 2 , 将 key2 的权重设置为 3
# 也就是 key1 中每个成绩 *2 , key2 中的每个成绩 *3
127.0.0.1:6379> zinterstore key4 2 key1 key2 weights 2 3 
(integer) 2
127.0.0.1:6379> zrange key4 0 -1 withscores
1) "zhangsan"
2) "65" # 10*2+15*3=65
3) "lisi"
4) "115" # 20*2+25*3=115

我们再指定一下 score 的计算方式

# 此时 , zhangsan 和 lisi 为交集结果
# 元素相同即可 , 不要求成绩相同
127.0.0.1:6379> zadd key1 10 zhangsan 20 lisi 30 wangwu
(integer) 3
127.0.0.1:6379> zadd key2 15 zhangsan 25 lisi 35 zhaoliu
(integer) 3# 将 key1 和 key2 的交集保存到 key5 中
# score 的计算方式为 max(key1.score,key2.score)
127.0.0.1:6379> zinterstore key5 2 key1 key2 aggregate max
(integer) 2
127.0.0.1:6379> zrange key5 0 -1 withscores
1) "zhangsan"
2) "15" # key2 中的 score 更高
3) "lisi"
4) "25" # key2 中的 score 更高
# 将 key1 和 key2 的交集保存到 key6 中
# score 的计算方式为 min(key1.score,key2.score)
127.0.0.1:6379> zinterstore key6 2 key1 key2 aggregate min
(integer) 2
127.0.0.1:6379> zrange key6 0 -1 withscores
1) "zhangsan"
2) "10" # key1 中的 score 更低
3) "lisi" 
4) "20" # key1 中的 score 更低

zinterstore 的时间复杂度为 O(N * K) + O(M * log(M))
我们来看一下官方文档对于时间复杂度的解释 : https://redis.io/commands/zinterstore
image.png
zinterstore 时间复杂度化简.png

时间复杂度我们不需要刻意记忆 , 我们只需要通过时间复杂度来去更好的理解命令即可

并集 : zunionstore

语法 : zunionstore destination numkeys key1 [key2 …] [weights weight1 [weight2 …]] [aggregate <sum | min | max>]
里面的用法和选项跟 zinterstore 类似 , 我们直接通过命令来了解它的用法

# 并集是 zhangsan、lisi、wangwu、zhaoliu
127.0.0.1:6379> zadd key1 10 zhangsan 20 lisi 30 wangwu
(integer) 3
127.0.0.1:6379> zadd key2 15 zhangsan 25 lisi 35 zhaoliu
(integer) 3
127.0.0.1:6379> zunionstore key3 2 key1 key2
(integer) 4 # 返回值表示交集元素的个数
127.0.0.1:6379> zrange key3 0 -1 withscores
# 顺序按照成绩重新进行升序排列
1) "zhangsan"
2) "25" # 默认是 sum(key1.score,key2.score)
3) "wangwu"
4) "30"
5) "zhaoliu"
6) "35"
7) "lisi"
8) "45"
# 将 key1 的权重设置成 0.5 , 将 key2 的权重设置成 0.6
# 也就是 key1 中的每个元素 *0.5 , key2 中的每个元素 *0.6
127.0.0.1:6379> zunionstore key4 2 key1 key2 weights 0.5 0.6
(integer) 4
127.0.0.1:6379> zrange key4 0 -1 withscores
1) "zhangsan"
2) "14" # 10*0.5+15*0.6=14
3) "wangwu"
4) "15" # 30*0.5=15
5) "zhaoliu"
6) "21" # 35*0.6=21
7) "lisi"
8) "25" # 20*0.5+25*0.6=25
# 将 key1 和 key2 的并集保存到 key5 中
# score 的计算方式为 min(key1.score,key2.score)
127.0.0.1:6379> zunionstore key5 2 key1 key2 aggregate min
(integer) 4
127.0.0.1:6379> zrange key5 0 -1 withscores
1) "zhangsan"
2) "10" # min(10,15)=10
3) "lisi"
4) "20" # min(20,25)=20
5) "wangwu"
6) "30" #min(30)=30
7) "zhaoliu"
8) "35" #min(35)=35
# 将 key1 和 key2 的并集保存到 key6 中
# score 的计算方式为 max(key1.score,key2.score)
127.0.0.1:6379> zunionstore key6 2 key1 key2 aggregate max
(integer) 4
127.0.0.1:6379> zrange key6 0 -1 withscores
1) "zhangsan"
2) "15" # max(10,15)=15
3) "lisi"
4) "25" # max(20,25)=25
5) "wangwu"
6) "30" # max(30)=30
7) "zhaoliu"
8) "35" # max(35)=35

小结

在这里插入图片描述

三 . 内部编码

有序集合内部的编码方式有两种 :

  1. 如果有序集合中的元素个数较少 , 或者单个元素体积不大 , 就会使用 ziplist 来去存储
  2. 如果有序集合中的元素个数较多 , 或者单个元素体积非常大 , 就会使用 skiplist (跳表) 来去存储

跳表就类似一个二叉搜索树 , 他查询元素的时间复杂度是 O(logN) . 但是跳表更适合按照范围来去获取元素

# 有序集合中的元素个数较少 && 单个元素体积不大 -> ziplist
127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu 
(integer) 3
127.0.0.1:6379> object encoding key
"ziplist"
# 有序集合中的元素个数较大 || 单个元素体积比较大 -> skiplist
127.0.0.1:6379> zadd key 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> object encoding key
"skiplist"

具体的跳表的相关细节 , 我们之后专门来去给大家介绍

四 . 应用场景 : 排行榜

关于排行榜 , 也有很多具体的说法 , 比如 : 微博热搜、游戏天梯排行、成绩排行 …
排行榜非常重要的要素就是用来排行的分数是实时变化的 , 那使用 Zset 来完成排行榜是非常简单的 .
比如游戏天梯排行 , 我们只需要把玩家信息和对应分数放到有序集合中即可 , 自动就形成了排行榜 . 我们随时可以按照排行 (下标) 或者按照分数进行范围查询来去生成排行榜 .
而且随着分数发生变化 , 可以使用 zincrby 来去修改分数 , 并且最终的排行顺序也可以自动调整 (O(logN) 的时间复杂度)

但是有的排行榜也会更复杂一些 , 比如微博热搜这种 , 他不只与浏览量有关 , 还需要考虑点赞量、转发量、评论量等等因素 , 那通过 Zset 实现也很简单 , 可以通过权重分配的方式 , 通过 zinterstore / zunionstore 来去计算热度 .
我们可以将浏览量、点赞量、评论量、转发量所对应的数值分别存放到不同的有序集合中 , member 就是微博的 ID , score 就是各自维度所对应的数值 , 我们通过 zinterstore / zunionstore 就可以把上述有序集合按照约定好的权重去进行集合间运算即可 , 那最后得到的结果集合就是我们所需要的热度 .


今天关于 zset 的分享就结束了 , 如果对你有帮助的话 , 还请一键三连~
在这里插入图片描述

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

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

相关文章

【算法】PageRank

一、引言 PageRank是由谷歌创始人拉里佩奇和谢尔盖布林在斯坦福大学读研究生时发明的一种算法&#xff0c;用于衡量网页的重要性。它基于一个简单的假设&#xff1a;更重要的网页会有更多的链接指向它。 二、算法原理 PageRank算法的核心思想是&#xff0c;一个网页的重要性可以…

沸点 | LDBC 第18届 TUC 会议召开,专家孙宇熙受邀参加并发表演讲

图数据管理领域国际权威组织LDBC&#xff08;Linked Data Benchmark Council&#xff09;于8月30日至31日在广州举办了第18届LDBC TUC会议。作为图数据库领域的创新引领者&#xff0c;嬴图受邀参加此次盛会&#xff0c;国际高性能计算与存储系统专家、大数据专家、图专家及嬴图…

【从零开始学爬虫】采集58同城房源数据

本文以采集北京市58同城房源数据为例进行演示&#xff1a; l 采集网站 【场景描述】采集58同城房源数据。 【使用工具】前嗅ForeSpider数据采集系统 http://www.forenose.com/view/commodity/forespider.html 【入口网址】 https://bj.58.com/xiaoqu/?PGTID0d000000-000…

三、数组————相关概念详解

数组 前言一、数据理论基础二、数组常用操作2.1 初始化数组2.2 访问数组中的元素2.3 插入元素2.4 删除元素 三、数组扩展3.1 遍历数组3.2 数组扩容 总结1、数组的优点2、数组的不足 前言 在数据结构中&#xff0c;数组可以算得上最基本的数据结构。数组可以用于实现栈、队列、…

YoloV10改进策略:卷积篇|基于PConv的二次创新|附结构图|性能和精度得到大幅度提高(独家原创)

文章目录 摘要论文指导PConv在论文中的描述改进YoloV10的描述改进代码与结构图改进方法测试结果总结摘要 在PConv的基础上做了二次创新,创新后的模型不仅在精度和速度上有了质的提升,还可以支持Stride为2的降采样。 改进方法简单高效,需要发论文的同学不要错过! 论文指导…

机器学习实战篇——肿瘤良性/恶性分类器(二元逻辑回归)

机器学习之实战篇——肿瘤良性/恶性分类器&#xff08;二元逻辑回归&#xff09; 前言数据集和实验文件下载相关文章推荐实验过程导入相关模块数据预处理手写二元逻辑回归模型&#xff08;小批量梯度下降&#xff09;sklearn逻辑回归器 前言 实验中难免有许多缺陷和错误&#…

Mac M1安装Hive

一、下载解压Hive 1.官网地址 https://dlcdn.apache.org/hive/ 2.选择对应版本进行下载&#xff0c;这里我以3.1.3为例&#xff1b; 3.下载好后&#xff0c;进行解压&#xff0c;并重命名为hive-3.1.3&#xff0c;放到资源库目录下&#xff1b; 二、配置系统环境 1.打开~/…

Hack The Box-Infiltrator【更新中】

信息收集&端口利用 nmap -sSVC infiltrator.htbStarting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-02 09:17 CST Nmap scan report for infiltrator.htb Host is up (0.61s latency). Not shown: 987 filtered tcp ports (no-response) PORT STATE SERVICE …

C++竞赛初阶L1-15-第六单元-多维数组(34~35课)551: T456501 计算矩阵边缘元素之和

题目内容 输入一个整数矩阵&#xff0c;计算位于矩阵边缘的元素之和。 所谓矩阵边缘的元素&#xff0c;就是第一行和最后一行的元素以及第一列和最后一列的元素。 输入格式 第 1 行包含两个整数&#xff0c;分别为行数 m 和列数 n&#xff0c;两个整数之间空格隔开。 第 2 …

【单调栈 】2289. 使数组按非递减顺序排列

本文涉及的基础知识点 单调栈分类、封装和总结 LeetCode2289. 使数组按非递减顺序排列 给你一个下标从 0 开始的整数数组 nums 。在一步操作中&#xff0c;移除所有满足 nums[i - 1] > nums[i] 的 nums[i] &#xff0c;其中 0 < i < nums.length 。 重复执行步骤&a…

Sobel算子,Scharr算子和Laplacian算子

图像边缘检测大幅度地减少了数据量&#xff0c;并且剔除了可以认为不相关的信息&#xff0c;保留了图像重要的结构属性。有许多方法用于边缘检测&#xff0c; 绝大部分可以划分为两类&#xff1a;基于搜索和基于零穿越。 基于搜索:通过寻找图像一阶导数中的最大值来检测边界&am…

4.1 数据分析-excel 基本操作

第四节&#xff1a;数据分析-excel 基本操作 课程目标 学会excel 基本操作 课程内容 数据伪造 产生一份招聘数据 import pandas as pd from faker import Faker import random import numpy as np# 创建一个Faker实例&#xff0c;用于生成假数据&#xff0c;指定中文本地…

c# 笔记 winform添加右键菜单,获取文件大小 ,多条件排序OrderBy、ThenBy,list<double>截取前5个

Winform右键菜单‌ 要在C# Winform应用程序中添加右键菜单&#xff0c;‌你可以按照以下步骤操作&#xff1a;‌ 1.‌创建菜单项‌ 在Form的构造函数或加载事件中&#xff0c;‌创建ContextMenuStrip控件的实例&#xff0c;‌并为其添加菜单项。‌ 2.‌绑定到控件‌ 将Con…

踩坑记录(序列化与反序列化)

问题描述 实体类中设定字段名称为 sValue和yValue 返回给前段后,变成了svalue,yvalue 字段设置 测试结果:与字段不符,匹配失败 解决方法 在字段上添加JsonProperty("字段名")注解

报告 | 以消费者为中心,消费品零售行业数字化建设持续深化

​2024年是“消费促进年”&#xff0c;国内消费市场稳步复苏。在消费需求多样化、国家政策的推动下&#xff0c;“数字化转型”仍是消费品零售行业的年度主题词&#xff0c;是品牌方获取核心竞争力的必要途径。消费品零售行业的数字化转型重心有所调整&#xff0c;从线上渠道布…

虚拟系统VS

定义 虚拟系统VS&#xff08;Virtual System&#xff09;是指将一台物理设备PS&#xff08;Physical System&#xff09;虚拟成多个相互隔离的逻辑系统。每个VS独立工作&#xff0c;在业务功能上等同于一台独立的传统物理设备&#xff0c;如图2-1所示。 目的 随着网络规模的不…

macos OneNote 2016 for Mac 官方pkg下载地址 - macos 10.15 Catalion 可用Onenote版本官方下载地址

macos 10.15 Catalion 版本的系统已经无法正常从应用商店下载到可用的Onenote 应用,原因是版本不受支持, 而且onenote官方链接的应用商店地址https://apps.apple.com/us/app/microsoft-onenote/id784801555?mt12在中国地区也无法访问, 所以中国地区用户如果想使用onenote应用…

C语言 | Leetcode C语言题解之第394题字符串解码

题目&#xff1a; 题解&#xff1a; #define N 2000typedef struct {int data[30];;int top; } Stack;void push(Stack *s, int e) { s->data[(s->top)] e; }int pop(Stack *s) { return s->data[--(s->top)]; }//多位数字串转换成int int strToInt(char *s) {cha…

Charles抓包全流程(Mac端+iOS端)

文章目录 与其他抓包软件的对比FiddlerWireShark Charles下载安装及配置Charles抓包实践小结 Charles Proxy是一个广泛使用的网络调试代理工具&#xff0c;它允许开发者监控和分析所有经过计算机的HTTP和SSL/HTTPS网络流量信息。 与其他抓包软件的对比 Fiddler Charles 支持多…

Leetcode3250. 单调数组对的数目 I

Every day a Leetcode 题目来源&#xff1a;3250. 单调数组对的数目 I 解法1&#xff1a;记忆化搜索 题目输入一个数组nums。 假设有两个数组A和B&#xff0c;A递增&#xff0c;B递减&#xff0c;且 Ai Bi numsi ​ 问有多少对(A,B)数组对。 解法&#xff1a; 代码&…