Redis 数据结构精讲:Set 集合的原理、命令与实战场景
大家好,今天我们来聊聊 Redis 中一个非常实用的数据结构 ------Set(集合) 。它和我们熟悉的列表(List)有很多相似之处,比如都能存储多个字符串类型的元素,但也有几个关键区别:Set 中的元素是无序 的,而且不允许重复。更重要的是,Redis 的 Set 天生支持交集、并集、差集这些集合操作,在很多业务场景下都能发挥奇效。
一、Set 集合的基本概念
先看个直观的例子,比如我们要记录用户 user:1 的兴趣爱好:
可以看到,Set 的 Key 是 user:1:subscribe,对应的 Value 是一个包含 Basketball、IT、Music 等多个元素的集合。这些元素没有固定顺序,也不会重复,完美契合了 "用户兴趣标签" 这类场景的需求。
二、Set 集合的基础命令详解
我们先从最常用的基础命令开始,逐个拆解它们的用法、时间复杂度和实战示例。
1. SADD:添加元素到集合
作用:将一个或多个元素添加到集合中,重复元素会被自动忽略。
-
语法 :
SADD key member [member \.\.\.\] -
时间复杂度:O(1)
-
返回值:本次成功添加的元素个数
redis
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SADD myset "World"
(integer) 0 # 重复元素添加失败,返回0
redis> SMEMBERS myset
1) "Hello"
2) "World"
2. SMEMBERS:获取集合中所有元素
作用:返回集合中的所有元素,注意元素的顺序是无序的。
-
语法 :
SMEMBERS key -
时间复杂度:O (N)(N 为集合元素个数)
-
返回值:集合中所有元素的列表
redis
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SMEMBERS myset
1) "Hello"
2) "World"
3. SISMEMBER:判断元素是否存在于集合中
作用:判断一个元素是否是集合的成员。
-
语法 :
SISMEMBER key member -
时间复杂度:O(1)
-
返回值:1 表示元素存在,0 表示元素不存在或集合本身不存在
redis
redis> SADD myset "one"
(integer) 1
redis> SISMEMBER myset "one"
(integer) 1
redis> SISMEMBER myset "two"
(integer) 0
4. SCARD:获取集合的元素个数
作用:获取集合的基数(cardinality),也就是集合中元素的数量。
-
语法 :
SCARD key -
时间复杂度:O(1)
-
返回值:集合中的元素个数
redis
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SCARD myset
(integer) 2
5. SPOP:随机删除并返回元素
作用:从集合中随机删除并返回一个或多个元素(因为集合是无序的,删除的元素是随机的)。
-
语法 :
SPOP key [count\] -
时间复杂度:O (N)(N 为 count 的值)
-
返回值:被删除的元素
redis
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myset "three"
(integer) 1
redis> SPOP myset
"one"
redis> SMEMBERS myset
1) "two"
2) "three"
redis> SADD myset "four"
(integer) 1
redis> SADD myset "five"
(integer) 1
redis> SPOP myset 3
1) "four"
2) "five"
3) "two"
6. SMOVE:移动元素到另一个集合
作用:将一个元素从源集合移动到目标集合中。
-
语法 :
SMOVE source destination member -
时间复杂度:O(1)
-
返回值:1 表示移动成功,0 表示元素不存在或移动失败
redis
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myotherset "three"
(integer) 1
redis> SMOVE myset myotherset "two"
(integer) 1
redis> SMEMBERS myset
1) "one"
redis> SMEMBERS myotherset
1) "three"
2) "two"
7. SREM:删除集合中的指定元素
作用:从集合中删除一个或多个指定的元素。
-
语法 :
SREM key member [member \.\.\.\] -
时间复杂度:O (N)(N 为要删除的元素个数)
-
返回值:本次操作成功删除的元素个数
redis
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myset "three"
(integer) 1
redis> SREM myset "one"
(integer) 1
redis> SMEMBERS myset
1) "two"
2) "three"
三、集合间的高级操作:交集、并集、差集
这是 Redis Set 最强大的功能之一,支持多个集合之间的数学运算,比如交集(inter)、并集(union)、差集(diff)。
1. SINTER:获取多个集合的交集
作用:返回所有给定集合的交集元素。
-
语法 :
SINTER key [key \.\.\.\] -
时间复杂度:O (N*M)(N 是最小集合的元素个数,M 是集合的数量)
-
返回值:交集的元素列表
redis
redis> SADD key1 "a"
(integer) 1
redis> SADD key1 "b"
(integer) 1
redis> SADD key1 "c"
(integer) 1
redis> SADD key2 "c"
(integer) 1
redis> SADD key2 "d"
(integer) 1
redis> SADD key2 "e"
(integer) 1
redis> SINTER key1 key2
1) "c"
2. SINTERSTORE:将交集结果保存到目标集合
作用 :和 SINTER 功能类似,但会把交集结果存储到指定的目标集合中,而不是直接返回。
-
语法 :
SINTERSTORE destination key [key \.\.\.\] -
时间复杂度:O(N*M)
-
返回值:交集中元素的个数
redis
redis> SADD key1 "a"
(integer) 1
redis> SADD key1 "b"
(integer) 1
redis> SADD key1 "c"
(integer) 1
redis> SADD key2 "c"
(integer) 1
redis> SADD key2 "d"
(integer) 1
redis> SADD key2 "e"
(integer) 1
redis> SINTERSTORE key key1 key2
(integer) 1
redis> SMEMBERS key
1) "c"
3. SUNION:获取多个集合的并集
作用:返回所有给定集合的并集元素(合并所有元素并去重)。
-
语法 :
SUNION key [key \.\.\.\] -
时间复杂度:O (N)(N 是所有集合元素的总数)
-
返回值:并集的元素列表
redis
redis> SADD key1 "a"
(integer) 1
redis> SADD key1 "b"
(integer) 1
redis> SADD key1 "c"
(integer) 1
redis> SADD key2 "c"
(integer) 1
redis> SADD key2 "d"
(integer) 1
redis> SADD key2 "e"
(integer) 1
redis> SUNION key1 key2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
4. SUNIONSTORE:将并集结果保存到目标集合
作用 :和 SUNION 功能类似,但会把并集结果存储到指定的目标集合中。
-
语法 :
SUNIONSTORE destination key [key \.\.\.\] -
时间复杂度:O(N)
-
返回值:并集中元素的个数
redis
redis> SADD key1 "a"
(integer) 1
redis> SADD key1 "b"
(integer) 1
redis> SADD key1 "c"
(integer) 1
redis> SADD key2 "c"
(integer) 1
redis> SADD key2 "d"
(integer) 1
redis> SADD key2 "e"
(integer) 1
redis> SUNIONSTORE key key1 key2
(integer) 5
redis> SMEMBERS key
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
5. SDIFF:获取多个集合的差集
作用:返回第一个集合与其他集合的差集元素(只保留第一个集合中独有的元素)。
-
语法 :
SDIFF key [key \.\.\.\] -
时间复杂度:O(N)
-
返回值:差集的元素列表
redis
redis> SADD key1 "a"
(integer) 1
redis> SADD key1 "b"
(integer) 1
redis> SADD key1 "c"
(integer) 1
redis> SADD key2 "c"
(integer) 1
redis> SADD key2 "d"
(integer) 1
redis> SADD key2 "e"
(integer) 1
redis> SDIFF key1 key2
1) "a"
2) "b"
6. SDIFFSTORE:将差集结果保存到目标集合
作用 :和 SDIFF 功能类似,但会把差集结果存储到指定的目标集合中。
-
语法 :
SDIFFSTORE destination key [key \.\.\.\] -
时间复杂度:O(N)
-
返回值:差集中元素的个数
redis
redis> SADD key1 "a"
(integer) 1
redis> SADD key1 "b"
(integer) 1
redis> SADD key1 "c"
(integer) 1
redis> SADD key2 "c"
(integer) 1
redis> SADD key2 "d"
(integer) 1
redis> SADD key2 "e"
(integer) 1
redis> SDIFFSTORE key key1 key2
(integer) 2
redis> SMEMBERS key
1) "a"
2) "b"
四、Set 命令速查表
这里整理了所有常用的 Set 命令,方便大家快速查阅:
| 命令 | 功能描述 | 时间复杂度 |
|---|---|---|
SADD key member [\.\.\.\] |
添加元素到集合 | O(1) |
SMEMBERS key |
获取集合所有元素 | O(N) |
SISMEMBER key member |
判断元素是否存在 | O(1) |
SCARD key |
获取集合元素个数 | O(1) |
SPOP key [count\] |
随机删除并返回元素 | O (N)(N 为 count) |
SMOVE src dest member |
移动元素到另一个集合 | O(1) |
SREM key member [\.\.\.\] |
删除指定元素 | O (N)(N 为删除的元素数) |
SINTER key1 key2 [\.\.\.\] |
获取多个集合的交集 | O(N*M) |
SINTERSTORE dest key1 [\.\.\.\] |
存储交集结果到目标集合 | O(N*M) |
SUNION key1 key2 [\.\.\.\] |
获取多个集合的并集 | O(N) |
SUNIONSTORE dest key1 [\.\.\.\] |
存储并集结果到目标集合 | O(N) |
SDIFF key1 key2 [\.\.\.\] |
获取多个集合的差集 | O(N) |
SDIFFSTORE dest key1 [\.\.\.\] |
存储差集结果到目标集合 | O(N) |
五、Set 集合的底层编码实现
Redis 的 Set 类型有两种底层编码方式,会根据场景自动切换:
-
intset(整数集合) :当集合中的元素都是整数,且元素个数不超过 512 个时,Redis 会使用
intset编码存储,这种方式非常节省内存。redis127.0.0.1:6379> SADD setkey 1 2 3 4 (integer) 4 127.0.0.1:6379> OBJECT encoding setkey "intset" -
hashtable(哈希表) :当集合中的元素包含非整数,或者元素个数超过 512 个时,Redis 会自动切换为
hashtable编码,以保证读写性能。redis127.0.0.1:6379> SADD setkey 1 2 3 4 ... (添加超过512个元素) 127.0.0.1:6379> OBJECT encoding setkey "hashtable"
六、Set 集合的实战场景
Set 集合的特性,让它在很多业务场景下都能大显身手,这里举几个常见的例子:
1. 给用户添加兴趣标签
我们可以用 Set 存储用户的兴趣标签,天然去重,添加 / 删除都很方便:
redis
SADD user:1:tags tag1 tag2 tag3
SADD user:1:tags tag1 tag4 # 重复的tag1会被忽略
2. 给标签添加用户
反过来,也可以用 Set 存储每个标签下的用户列表:
redis
SADD tag:1:users user:1 user:2 user:3
SADD tag:2:users user:1 user:4 user:9
3. 计算用户的共同好友 / 共同兴趣
利用 Set 的交集功能,我们可以轻松计算两个用户的共同兴趣标签:
redis
# 计算user:1和user:2的共同兴趣
SINTER user:1:tags user:2:tags
这种场景在社交平台、推荐系统中非常常见,比如:
-
计算两个用户的共同好友
-
找出同时关注了多个话题的用户
-
统计用户的共同兴趣,实现个性化推荐
总结
Redis 的 Set 集合虽然结构简单,但功能非常强大:无序、去重、支持丰富的集合运算,在标签系统、好友关系、数据去重等场景中都有不可替代的优势。掌握好这些命令和底层原理,就能在开发中灵活运用,写出更高效、更优雅的代码。