Redis 集合类型详解:Set 与 Zset

目录

  • [1. Set 集合](#1. Set 集合)
    • [1.1 普通命令](#1.1 普通命令)
    • [1.2 集合间操作](#1.2 集合间操作)
    • [1.3 内部编码](#1.3 内部编码)
    • [1.4 使用场景](#1.4 使用场景)
  • [2. Zset 有序集合](#2. Zset 有序集合)
    • [2.1 普通命令](#2.1 普通命令)
    • [2.2 集合间操作](#2.2 集合间操作)
    • [2.3 内部编码](#2.3 内部编码)
    • [2.4 应用场景](#2.4 应用场景)
  • [3. 小结与经典面试题](#3. 小结与经典面试题)
    • [3.1 小结](#3.1 小结)
    • [3.2 经典面试题](#3.2 经典面试题)

✅️ 1.Set集合

集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 1)元素之间是⽆序

的 2)元素不允许重复。⼀个集合中最多可以存储 2 ^ − 32 - 1 个元素。Redis 除了⽀持集合内的增删查改操作,同时还⽀持多个集合取交集、并集、差集。

1.1 普通命令

🌻SADD:将一个元素或者多个元素加入到set中。注意,重复的元素无法添加到set中。

语法 :SADD key element [element ...]

时间复杂度 :O(1)

返回值 :添加成功的元素个数

示例:

bash 复制代码
127.0.0.1:6379> sadd set "apple"
(integer) 1
127.0.0.1:6379> sadd set "orange"
(integer) 1
127.0.0.1:6379> sadd set "pear" peach
(integer) 2
127.0.0.1:6379> smembers set
1) "peach"
2) "pear"
3) "apple"
4) "orange"

🌻SMEMBERS:获取一个set中的所有元素,注意,元素之间的顺序是无序的。

语法 :SMEMBERS key

时间复杂度 :O(N)

返回值 :所有元素的列表

示例:

bash 复制代码
127.0.0.1:6379> sadd set "apple"
(integer) 1
127.0.0.1:6379> sadd set "orange"
(integer) 1
127.0.0.1:6379> sadd set "pear" peach
(integer) 2
127.0.0.1:6379> smembers set
1) "peach"
2) "pear"
3) "apple"
4) "orange"

🌻SISMEMBER:判断一个元素是否在set集合中。

语法 :SISMEMBER key menber

时间复杂度 :O(1)

返回值 :1表示元素在set中,0表示元素不在set中或者key不存在。

示例:

bash 复制代码
127.0.0.1:6379> smembers set
1) "peach"
2) "pear"
3) "apple"
4) "orange"
127.0.0.1:6379> sismember set apple
(integer) 1
127.0.0.1:6379> sismember set berry
(integer) 0

🌻SCARD:获取一个set的基数,即set中的元素个数。

语法 :SCARD key

时间复杂度 :O(1)

返回值 :set中的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> smembers set
1) "peach"
2) "pear"
3) "apple"
4) "orange"
127.0.0.1:6379> scard set
(integer) 4

🌻SPOP:从set中删除并返回一个或者多个元素。注意,由于set中的元素是无序的,所以取出哪个元素实际是未定义行为,既可以看作是随机的。

语法 :SPOP key [count]

时间复杂度 :O(N)

返回值 :取出的元素。

示例:

bash 复制代码
127.0.0.1:6379> smembers set
1) "peach"
2) "pear"
3) "apple"
4) "orange"
127.0.0.1:6379> spop set 
"apple"
127.0.0.1:6379> sadd set berry
(integer) 1
127.0.0.1:6379> smembers set
1) "peach"
2) "pear"
3) "berry"
4) "orange"
127.0.0.1:6379> spop set 2
1) "peach"
2) "orange"
127.0.0.1:6379> smembers set
1) "pear"
2) "berry"

🌻SMOVE:将一个元素从源set中取出并放入目标set中。

语法 :SMOVE source_set destination_set element

时间复杂度 :O(1)

返回值 :1表示移动成功,0表示移动失败。

示例:

bash 复制代码
127.0.0.1:6379> sadd vegetable tomato onion
(integer) 2
127.0.0.1:6379> smembers vegetable
1) "tomato"
2) "onion"
127.0.0.1:6379> smembers set
1) "pear"
2) "berry"
127.0.0.1:6379> smove set vegetable "pear"
(integer) 1
127.0.0.1:6379> smembers set
1) "berry"
127.0.0.1:6379> smembers vegetable
1) "pear"
2) "tomato"
3) "onion"

🌻SREM:将指定的元素从set中删除。

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

时间复杂度 :O(N),N表示要删除的元素个数

返回值 :删除的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> smembers vegetable
1) "pear"
2) "tomato"
3) "onion"
127.0.0.1:6379> srem vegetable onion pear
(integer) 2
127.0.0.1:6379> smembers vegetable
1) "tomato"

1.2 集合间操作

🌻SINTER:获取给定set的交集中的元素。

语法 :SINTER key [key ...]

时间复杂度 :O(N*M),N是最小的集合元素个数,M是最大的集合元素个数。

返回值 :交集的个数。

示例:

bash 复制代码
127.0.0.1:6379> sadd set1 aaa bbb ccc
(integer) 3
127.0.0.1:6379> sadd set2 ddd eee aaa
(integer) 3
127.0.0.1:6379> sadd set3 fff ccc xxx aaa ggg
(integer) 5
127.0.0.1:6379> sinter set1 set2 set3
1) "aaa"

🌻SINTERSTORE:获取给定set的交集中的元素并保存到目标set中。

语法 :SINTERSTORE destination key [key ...]

时间复杂度 :O(N*M),N是最小的集合元素个数,M是最大的集合元素个数。

返回值 :交集的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> sadd set1 aaa bbb ccc
(integer) 3
127.0.0.1:6379> sadd set2 ddd eee aaa
(integer) 3
127.0.0.1:6379> sadd set3 fff ccc xxx aaa ggg
(integer) 5
127.0.0.1:6379> sinterstore set set1 set2 set3
(integer) 1
127.0.0.1:6379> smembers set
1) "aaa"

🌻SUNION:获取给定set的并集中的元素。

语法 :SINTERSTORE destination key [key ...]

时间复杂度 :O(N) N是给定的所有集合的总的元素个数

返回值 :并集的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> sadd set1 aaa bbb ccc
(integer) 3
127.0.0.1:6379> sadd set2 ddd eee aaa
(integer) 3
127.0.0.1:6379> sadd set3 fff ccc xxx aaa ggg
(integer) 5
127.0.0.1:6379> sunion set1 set2 set3
1) "bbb"
2) "ggg"
3) "eee"
4) "ccc"
5) "ddd"
6) "xxx"
7) "fff"
8) "aaa"

🌻SUNIONSTORE:获取给定set的并集中的元素并保存到目标set中。

语法 :SUNIONSTORE destination key [key ...]

时间复杂度 :O(N),N是给定的所有集合的总的元素个数。

返回值 :并集的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> sadd set1 aaa bbb ccc
(integer) 3
127.0.0.1:6379> sadd set2 ddd eee aaa
(integer) 3
127.0.0.1:6379> sadd set3 fff ccc xxx aaa ggg
(integer) 5
127.0.0.1:6379> sunionstore unionSet set1 set2 set3
(integer) 8
127.0.0.1:6379> smembers unionSet
1) "bbb"
2) "ggg"
3) "eee"
4) "ccc"
5) "ddd"
6) "xxx"
7) "fff"
8) "aaa"

🌻SDIFF:获取给定set的差集中的元素。

key1 和key2的差集,表示key1中存在,在key2中不存在的元素。

语法 :SDIFF key1 [key ...]

时间复杂度 :O(N),N是给定的所有集合的总的元素个数。

返回值 :差集的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> sadd set1 111 222 333
(integer) 3
127.0.0.1:6379> sadd set2 111 444 555 222
(integer) 4
127.0.0.1:6379> sdiff set1 set2
1) "333"

🌻SDIFFSTORE:获取给定set的差集中的元素并保存到目标set中。

语法 :SDIFFSTORE destination key [key ...]

时间复杂度 :O(N),N是给定的所有集合的总的元素个数。

返回值 :差集的元素个数。

示例:

bash 复制代码
127.0.0.1:6379> sadd set1 111 222 333
(integer) 3
127.0.0.1:6379> sadd set2 111 444 555 222
(integer) 4
127.0.0.1:6379> sdiffstore diffSet set1 set2
(integer) 1
127.0.0.1:6379> smembers diffSet
1) "333"

1.3 内部编码
1.4 使用场景

集合类型比较典型的应用场景就是标签,每个用户会对不同的板块感兴趣,比如用户A对体育和游戏板块感兴趣,用户B对编程和娱乐感兴趣。这些模块就可以标签化,然后使用Redis中的集合来存储。下面使用一个例子来演示使用集合来实现标签的不同管理功能:

① 给用户添加标签

bash 复制代码
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4

② 给标签添加用户

bash 复制代码
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:4 user:9 user:28

③ 删除用户下的标签

bash 复制代码
srem user:1:tags tag1 tag5
...

④ 删除标签下的用户

bash 复制代码
srem tag1:users user:1
srem tag5:users user:1
...

⑤ 计算用户的共同兴趣标签

bash 复制代码
sinter user:1:tags user:2:tags

✅️ 2.Zset有序集合

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

☀️ 提示

zset排序规则:给zset中的member同时引入了一个属性,分数(score)浮点类型,每个member都会安排一个分数

zset主要还是用来存放member的,对于分数只是辅助.

2.1 普通命令

🌻ZADD:添加或者更新指定的元素以及关联的分数到zset中,分数雨刮符合double类型,+inf和-inf作为正负极限也是合法的。

语法 :ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]

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

返回值:本次添加成功的元素个数。

💡 参数说明

XX:仅仅⽤于更新已经存在的元素,不会添加新元素。类似修改功能

NX:仅⽤于添加新元素,不会更新已经存在的元素。类似新增功能

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

INCR:此时命令类似ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。

LT:less than

GT:``greater than

XX中的X可以理解为exist中的X

示例:

bash 复制代码
127.0.0.1:6379> zadd zset 1 "one"
(integer) 1
127.0.0.1:6379> zadd zset 1 uno
(integer) 1
127.0.0.1:6379> zadd zset 2 two 3 three
(integer) 2
127.0.0.1:6379> zrange zset 0 -1 withscores  # 显示所有元素,并带上分数
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
127.0.0.1:6379> zrange zset 0 -1
1) "one"
2) "uno"
3) "two"
4) "three"
127.0.0.1:6379> zadd zset 100 one 200 two 300 three  # 默认返回新增元素个数,此处是修改没有新增,所以返回0
(integer) 0
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "uno"
2) "1"
3) "one"
4) "100"
5) "two"
6) "200"
7) "three"
8) "300"
127.0.0.1:6379> zadd zset ch 10 one 20 two 30 three
(integer) 3
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "uno"
2) "1"
3) "one"
4) "10"
5) "two"
6) "20"
7) "three"
8) "30"
127.0.0.1:6379> zadd zset xx 1 one 2 two 3 three 4 four 5 five
(integer) 0
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
127.0.0.1:6379> zadd zset NX 1000 one 2000 two 3000 three 4000 four 5000 five
(integer) 2  # 4000 four 5000 five为新增元素
127.0.0.1:6379> zrange zset 0 -1 withscores
 1) "one"
 2) "1"
 3) "uno"
 4) "1"
 5) "two"
 6) "2"
 7) "three"
 8) "3"
 9) "four"
10) "4000"
11) "five"
12) "5000"
127.0.0.1:6379> zadd zset INCR 10 one # 将元素one的分数加上10
"11"
127.0.0.1:6379> zrange zset 0 -1 withscores
 1) "uno"
 2) "1"
 3) "two"
 4) "2"
 5) "three"
 6) "3"
 7) "one"
 8) "11"
 9) "four"
10) "4000"
11) "five"
12) "5000"
127.0.0.1:6379> zadd zset -inf "negative infinity" +inf "positive infinity"
(integer) 2
127.0.0.1:6379> zrange zset 0 -1 withscores
 1) "negative infinity"
 2) "-inf"
 3) "uno"
 4) "1"
 5) "two"
 6) "2"
 7) "three"
 8) "3"
 9) "one"
10) "11"
11) "four"
12) "4000"
13) "five"
14) "5000"
15) "positive infinity"
16) "inf"

🌻ZCARD:获取一个zset的基数(cardinality),即zset集合中的元素个数。

语法 :ZCARD key

时间复杂度 :O(1)。

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

示例:

bash 复制代码
127.0.0.1:6379> zrange zset 0 -1 withscores
 1) "negative infinity"
 2) "-inf"
 3) "uno"
 4) "1"
 5) "two"
 6) "2"
 7) "three"
 8) "3"
 9) "one"
10) "11"
11) "four"
12) "4000"
13) "five"
14) "5000"
15) "positive infinity"
16) "inf"
127.0.0.1:6379> zcard zset
(integer) 8

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

语法 :ZCOUN key min max

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

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

示例:

bash 复制代码
127.0.0.1:6379> zadd zset 100 "apple"
(integer) 1
127.0.0.1:6379> zadd zset 200 "orange"
(integer) 1
127.0.0.1:6379> zadd zset 300 "pear"
(integer) 1
127.0.0.1:6379> zcount zset -inf +inf
(integer) 3
127.0.0.1:6379> zcount zset 100 300 # 区间统计
(integer) 3
127.0.0.1:6379> zcount zset (100 300  # 区间排除
(integer) 2
127.0.0.1:6379> zcount zset (100 (300  # 排除具体元素
(integer) 1

🦞 拓展;

1.zset中的element和score的关系不是键值对的关系,可以相互查询。

2.先根据min找到对应的元素,再根据max找到对应的元素

3.实际上,zset中会记录每个元素的当前的排行/次序,查询到元素,就直接知道元素的次序,就可以直接把max对应的元素次序和min对应的元素次序,做减法即可。而不是采用遍历zset的方式,这样的时复杂度维O(logN + M).

4.对于min 和 max是可以写成浮点数的(zset分数本身就是浮点数),有两个特殊的浮点数:-inf 和 inf,即无穷大和负无穷大.


🌻ZRANGE:返回指定区间内的元素,分数按照升序排列,也可以带上参数WITHSCORES把分数也返回。

语法 :ZRANGE key start end [WITHSCORES]

时间复杂度 :O(log(N) + M)。此处要根据下标找到边界值,找到start和end对应的位置,然后开始遍历得到需要查看的元素.

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

示例:

bash 复制代码
127.0.0.1:6379> zrange zset 0 -1
1) "apple"
2) "orange"
3) "pear"
127.0.0.1:6379> zrange zset 0 -1 withscores   # 分数按照升序排列
1) "apple"
2) "100"
3) "orange"
4) "200"
5) "pear"
6) "300" 
127.0.0.1:6379> zrange zset 0 1
1) "apple"
2) "orange"
127.0.0.1:6379> zrange zset -2 -1 
1) "orange"
2) "pear"

🌻ZREVRANGE:返回指定区间内的元素,分数按照降序排列,也可以带上参数WITHSCORES把分数也返回。

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

语法 :ZREVRANGE key start stop [WITHSCORES]

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

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

示例:

bash 复制代码
127.0.0.1:6379> zrevrange zset 0 -1
1) "pear"
2) "orange"
3) "apple"
127.0.0.1:6379> zrevrange zset 0 -1 withscores
1) "pear"
2) "300"
3) "orange"
4) "200"
5) "apple"
6) "100"

🌻ZRANGEBYSCORE:返回分数在min和max之间的元素,默认去开情况下,min和max都是包含的,通过(排除。

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

语法 :ZRANGEBYSCORE key min max [WITHSCORES]

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

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

示例:

bash 复制代码
127.0.0.1:6379> zrangebyscore zset -inf +inf withscores
1) "apple"
2) "100"
3) "orange"
4) "200"
5) "pear"
6) "300"
127.0.0.1:6379> zrangebyscore zset 100 300 withscores
1) "apple"
2) "100"
3) "orange"
4) "200"
5) "pear"
6) "300"
127.0.0.1:6379> zrangebyscore zset 100 300
1) "apple"
2) "orange"
3) "pear"

🌻ZPOPMAX | ZPOPMIN:返回分数最高或者分数最低的count个元素。

语法 :ZPOPMAX | ZPOPMIN key [count]

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

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

示例:

bash 复制代码
127.0.0.1:6379> zrange zset 0 -1 WITHSCORES
1) "apple"
2) "100"
3) "orange"
4) "200"
5) "pear"
6) "300"
127.0.0.1:6379> zpopmax zset 2
1) "pear"
2) "300"
3) "orange"
4) "200"
127.0.0.1:6379> zrange zset 0 -1 WITHSCORES
1) "apple"
2) "100"
127.0.0.1:6379> zpopmin zset 
1) "apple"
2) "100"
127.0.0.1:6379> zrange zset 0 -1
(empty list or set)

💡 拓展

如果存入多个元素,且多个元素分数相同,同为最大值,zpopmax删除的时候,只删除其中的一个元素

分数。虽然分数是主要因素,但是分数相同会按照element字符串的字典序来决定先后。


🌻BZPOPMAX | BZPOPMIN:ZPOPMAX和BZPOPMIN的阻塞版本。

阻塞也是在有序集合为空的时候触发阻塞,阻塞到其他客户端插入元素。

如果有序集合中以及存在元素,会立即返回。

key标识了有序集合,bzpopmax可以同时读多个有序集合的。

语法 :BZPOPMAX | BZPOPMIN key [key ...] timeout # timeout表示超时时间 最多阻塞多久,单位是秒,支持小数形式,写作0.1就是100ms。

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

返回值 :元素列表。

示例:

bash 复制代码
redis> DEL zset1 zset2
(integer) 0
redis> ZADD zset1 0 a 1 b 2 c
(integer) 3
redis> BZPOPMAX zset1 zset2 0
1) "zset1"
2) "c"
3) "2

💡 拓展

如果当前zpopmax同时监听了多个key,假设key是M个,此时的时间复杂度还是O(logN),而不是O(logN * M),对于后者,是在 每个这样的key上面都删除一个元素,而前者是在这若干个key中只删除一次。


🌻ZRANK:`返回指定元素的排名,在升序序列中的排名。

语法 :ZRANK key element

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

返回值 :排名。

示例:

bash 复制代码
127.0.0.1:6379> zadd zset 100 "pear"
(integer) 1
127.0.0.1:6379> zadd zset 200 "apple"
(integer) 1
127.0.0.1:6379> zadd zset 300 "orange"
(integer) 1
127.0.0.1:6379> zrank zset pear  # 排名从0开始,即最小的元素的排名是0
(integer) 0
127.0.0.1:6379> zrank zset orange
(integer) 2
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "pear"
2) "100"
3) "apple"
4) "200"
5) "orange"
6) "300"

# ZREVRANK 返回降序序列中的指定元素的排名,最大的元素排名依然是0,然后依次增大
127.0.0.1:6379> zrevrank zset pear
(integer) 2
127.0.0.1:6379> zrevrank zset orange
(integer) 0

💡 拓展

如果当前zpopmax同时监听了多个key,假设key是M个,此时的时间复杂度还是O(logN),而不是O(logN * M),对于后者,是在 每个这样的key上面都删除一个元素,而前者是在这若干个key中只删除一次。


🌻ZSCORE:返回指定元素的分数。

此处 redis 对于这样的查询操作做了特殊的优化,付出了额外的空间代价,针对这里进行了优化到 O(1)的操作.

语法 :ZSCORE key element

时间复杂度 :O(1)。

返回值 :排名。

示例:

bash 复制代码
127.0.0.1:6379> zscore zset apple
"200"
127.0.0.1:6379> zscore zset orange
"300"
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "pear"
2) "100"
3) "apple"
4) "200"
5) "orange"
6) "300"

🌻ZREM:删除指定的一个或者多个元素。

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

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

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

示例:

bash 复制代码
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "pear"
2) "100"
3) "apple"
4) "200"
5) "orange"
6) "300"
127.0.0.1:6379> zrem zset orange pear
(integer) 2
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "apple"
2) "200"

🌻ZREMRANGEBYRANK:按照升序删除指定返回的元素,左闭右闭。

语法 :ZREMRANGEBYRANK key start stop

时间复杂度 :O(M *+ log(N))。N表示有序集合zset中的元素个数,M表示start - stop 区间中的元素个数

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

示例:

bash 复制代码
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "apple"
2) "200"
127.0.0.1:6379> zadd zset 100 pear
(integer) 1
127.0.0.1:6379> zadd zset 300 orange
(integer) 1
127.0.0.1:6379> zremrangebyrank zset 0 1
(integer) 2
127.0.0.1:6379> zrange zset 0 -1
1) "orange"

# ZREMRANGEBYSCORE 按照分数杉树指定范围的元素,左闭右闭
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "orange"
2) "300"
127.0.0.1:6379> zadd zset 100 pear
(integer) 1
127.0.0.1:6379> zadd zset 200 orange
(integer) 0
127.0.0.1:6379> zremrangebyscore zset 200 300
(integer) 1
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "pear"
2) "100"

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

不光会修改分数集合,也能同时修改元素位置,保持此时的集合是有序的。

语法 :ZINCRBY key increment member

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

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

示例:

bash 复制代码
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "pear"
2) "100"
127.0.0.1:6379> zincrby zset 1000 pear
"1100"
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "pear"
2) "1100"

2.2 集合间操作

① ZIINTESTORE

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

ZINTER,ZUNION,ZDIFF命令都是Redis6.2版本后才开始执行的。

语法 :ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]

💡 参数说明:

1.destination 表示要将合并后的结果存储到你也跟key对应的zset中。

2.numkeys是一个整数,描述后续有几个key参与运算,主要是因为numberkeys描述出keys的个数之后,就可以明确的知道,后面的选项是从哪里开始的,避免选项和keys混淆了。

3.有序集合中,member才是主体,而score只是辅助排序的工具人而已。因此,在进行比较"相同"的时候,只要member相同即可,score不相同也没关系,如果member相同,score不同,在进行交集合并之后的最终分数,咋算??此时就需要最后一个参数聚合方式 AGGREGATE中的三种方式。

sum表示将member相同的元素分数进行权重求和 作为最终结果

max选择最大值作为结果,min表示选择最小值作为最终结果。

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

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

示例:

bash 复制代码
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom

(integer) 6
127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
(integer) 4
127.0.0.1:6379> zrange user:ranking:1 0 -1 withscores
 1) "kris"
 2) "1"
 3) "mike"
 4) "91"
 5) "frank"
 6) "200"
 7) "tim"
 8) "220"
 9) "martin"
10) "250"
11) "tom"
12) "251"
127.0.0.1:6379> zrange user:ranking:2 0 -1 withscores
1) "james"
2) "8"
3) "mike"
4) "77"
5) "martin"
6) "625"
7) "tom"
8) "888"
127.0.0.1:6379> zinterstore sumRet 2 user:ranking:1 user:ranking:2 weights 0.5 2 aggregate sum
(integer) 3
127.0.0.1:6379> zrange sumRet 0 -1 withscores
1) "mike"
2) "199.5"
3) "martin"
4) "1375"
5) "tom"
6) "1901.5"
127.0.0.1:6379> zinterstore minRet 2 user:ranking:1 user:ranking:2  aggregate min
(integer) 3
127.0.0.1:6379> zinterstore maxRet 2 user:ranking:1 user:ranking:2  aggregate max
(integer) 3
127.0.0.1:6379> zrange minRet 0 -1 withscores
1) "mike"
2) "77"
3) "martin"
4) "250"
5) "tom"
6) "251"
127.0.0.1:6379> zrange maxRet 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "625"
5) "tom"
6) "888"

② ZUNIONSTORE

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

语法 :ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]

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

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

示例:

bash 复制代码
127.0.0.1:6379> zunionstore sumUnion 2 user:ranking:1 user:ranking:2 weights 0.5 2 aggregate sum
(integer) 7
127.0.0.1:6379> zrange sumUnion 0 -1 withscores
 1) "kris"
 2) "0.5"
 3) "james"
 4) "16"
 5) "frank"
 6) "100"
 7) "tim"
 8) "110"
 9) "mike"
10) "199.5"
11) "martin"
12) "1375"
13) "tom"
14) "1901.5"
127.0.0.1:6379> zunionstore minUnion 2 user:ranking:1 user:ranking:2 aggregate min
(integer) 7
127.0.0.1:6379> zunionstore maxUnion 2 user:ranking:1 user:ranking:2 aggregate max
(integer) 7
127.0.0.1:6379> zrange minUnion 0 -1 withscores
 1) "kris"
 2) "1"
 3) "james"
 4) "8"
 5) "mike"
 6) "77"
 7) "frank"
 8) "200"
 9) "tim"
10) "220"
11) "martin"
12) "250"
13) "tom"
14) "251"
127.0.0.1:6379> zrange maxUnion 0 -1 withscores
 1) "kris"
 2) "1"
 3) "james"
 4) "8"
 5) "mike"
 6) "91"
 7) "frank"
 8) "200"
 9) "tim"
10) "220"
11) "martin"
12) "625"
13) "tom"
14) "888"
2.3 内部编码
2.4 应用场景

✅️ 3. 小结与经典面试题

3.1 小结

本文围绕 Redis 的两种集合类型展开:

  • Set(集合) :无序、元素不重复,底层根据元素数量和大小在 intset 与 hashtable 之间切换。常用命令包括 SADD、SMEMBERS、SISMEMBER、SCARD、SPOP、SMOVE、SREM,以及集合间的 SINTER、SUNION、SDIFF 及其 *STORE 变体。典型应用是标签系统 :给用户打标签、给标签关联用户、删除标签、用 SINTER 计算共同兴趣。
  • Zset(有序集合) :元素不重复,但每个 member 关联一个浮点型 score,按 score 排序,底层在 ziplist 与 skiplist 之间切换。常用命令包括 ZADD、ZCARD、ZCOUNT、ZRANGE、ZREVRANGE、ZRANGEBYSCORE、ZPOPMAX/ZPOPMIN、BZPOPMAX/BZPOPMIN、ZRANK/ZREVRANK、ZSCORE、ZREM、ZREMRANGEBYRANK/ZREMRANGEBYSCORE、ZINCRBY,集合间操作为 ZINTERSTORE、ZUNIONSTORE,支持 WEIGHTS 权重与 AGGREGATE SUM|MIN|MAX 聚合。典型应用是排行榜。

一句话记忆:Set 管"有没有",Zset 管"排第几"。

3.2 经典面试题

Q1:Set 和 List 有什么区别?

List 有序、允许重复、按下标访问;Set 无序、不允许重复、按成员判断存在性。需要"去重 + 快速判存"用 Set,需要"顺序 + 可重复"用 List。

Q2:Set 的底层编码是什么?何时转换?

元素都是整数且个数不超过 set-max-intset-entries(默认 512)时用 intset;否则用 hashtable。intset 在元素过多或插入非整数元素时升级为 hashtable,且不可逆。

Q3:如何用 Redis 实现"共同好友"?

用 Set 存储每个用户的好友集合,SINTER user:1:friends user:2:friends 得到共同好友;SDIFF 可求"我关注但他没关注我"的差集;SUNION 求并集。数据量大时注意 SINTER 的 O(N*M) 开销,可先按 SCARD 选最小集合做驱动。

Q4:SPOP 和 SRANDMEMBER 的区别?

SPOP 会删除 并返回元素,SRANDMEMBER 只返回不删除。SPOP 适合抽奖、随机派发等一次性消费场景。

Q5:Zset 的底层结构是什么?为什么用跳表而不是红黑树?

元素较少时用 ziplist(7.0 后为 listpack),超过 zset-max-ziplist-entries(默认 128)或单个元素超过 zset-max-ziplist-value(默认 64 字节)时转为 skiplist + dict。跳表实现简单、范围查询(ZRANGE、ZRANGEBYSCORE)天然高效,且并发/内存控制更友好;dict 则让 ZSCORE 达到 O(1)。

Q6:ZADD 的 NX、XX、GT、LT、CH、INCR 分别是什么含义?

  • NX:只新增,不更新已存在元素;
  • XX:只更新已存在元素,不新增;
  • GT:仅当新分数大于当前分数时才更新;
  • LT:仅当新分数小于当前分数时才更新;
  • CH:返回值包含"被修改"的元素个数,而不只是新增个数;
  • INCR:等价于 ZINCRBY,对单个元素做分数累加。

Q7:ZINTERSTORE 中 numkeys 为什么必须显式指定?

因为后面还有 WEIGHTS、AGGREGATE 等可选参数,Redis 需要靠 numkeys 明确"前几个参数是 key",避免把选项误当成 key 解析。

Q8:ZINTERSTORE 的 WEIGHTS 和 AGGREGATE 如何配合?

先对每个集合的分数乘以对应权重,再按 AGGREGATE 指定的方式(SUM 求和、MIN 取最小、MAX 取最大)合并同一 member 的分数。例如 weights 0.5 2 aggregate sum 表示第一个集合分数乘 0.5、第二个乘 2 后相加。

Q9:ZRANGE 和 ZRANGEBYSCORE 的时间复杂度为什么是 O(log(N) + M)?

zset 内部维护了按分数有序的跳表,定位边界只需 O(log(N)),之后顺序遍历输出 M 个元素,因此总复杂度为 O(log(N) + M),而不是全量扫描。

Q10:如何用 Zset 实现排行榜?

ZADD rank score member 写入分数,ZREVRANGE rank 0 9 WITHSCORES 取 Top10,ZRANK/ZREVRANK 查某人的名次,ZINCRBY 做分数累加(如点赞数 +1),ZREMRANGEBYRANK 定期清理榜单尾部。注意大 key 问题,可按时间/业务维度分片。

Q11:ZPOPMAX 与 BZPOPMAX 的区别?

ZPOPMAX 非阻塞,集合为空时立即返回空;BZPOPMAX 是阻塞版本,集合为空时阻塞等待其他客户端写入,直到超时(timeout 秒,支持小数)。适合做阻塞式任务队列。

Q12:Zset 中分数相同的元素如何排序?

按 member 的字典序(二进制比较)升序排列。因此 ZRANGE 在分数相同时结果是确定的,不会出现随机顺序。

相关推荐
wo_jack1 小时前
C++ 入门笔记(十七):list 入门 —— 为什么 list 不能 sort?
数据结构·c++·面试·stl·list
鶴哥只手遮天2 小时前
《Redis 8.10.3 深度剖析》系列 ·第 17 章. 一个迟到十六年的类型
redis·后端·开源
Rain的Java大神之路3 小时前
设计一套生产级 API 接口:从契约、安全、幂等到限流,我踩过的坑都在这了
java·后端·面试
小南家的青蛙4 小时前
微服务体系下DevOps运维岗位面试方案设计
运维·面试·职场和发展·devops
IT规划师4 小时前
Redis 数据类型全解 - 数据类型选型矩阵:一张表、一棵树、四条线(十二)
redis·数据类型
虹科网络安全13 小时前
Redis Radar:在统一视图中掌握 Redis 部署、健康与容量
经验分享·redis·艾体宝·艾体宝it·艾体宝redis
大大大大晴天15 小时前
每天认识一个组件:Redis
redis