Redis 常见数据类型-Set 类型
类型简介
集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 1)元素之间是⽆序的 2)元素不允许重复,
⼀个集合中最多可以存储 $2^{32}$ - 1 个元素。Redis 除了⽀持集合内的增删查改操作,同时还⽀持多个集合取交集、并集、差集,合理
地使⽤好集合类型,能在实际开发中解决很多问题
相关指令如下:
相关指令
sadd
命令格式:
SADD key member [member ...]
将一个或多个元素添加到 set 中,重复定义的元素是无法添加到 set 中的
时间复杂度:O(1)
返回值:本次添加成功的个数
使用示例:
# 预期添加 5 个元素
127.0.0.1:6379> sadd k1 1 2 3 3 4
# 元素3被重复添加,只能添加一个
(integer) 4
127.0.0.1:6379> smembers k1
1) "1"
2) "2"
3) "3"
4) "4"
smembers
命令格式:
SMEMBERS key
获取—个 set 中的所有元素,元素间的顺序是无序的
时间复杂度:O(N)
返回值:所有元素的列表
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4
(integer) 4
127.0.0.1:6379> smembers k1
1) "1"
2) "2"
3) "3"
4) "4"
sismember
命令格式:
SISMEMBER key member
判断一个元素是否在 set 中
时间复杂度:O(1)
返回值:1表示存在,0表示不存在
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4
(integer) 4
# 查询元素 2 存在
127.0.0.1:6379> sismember k1 2
(integer) 1
# 查询元素 5 不存在
127.0.0.1:6379> sismember k1 5
(integer) 0
scard
命令格式:
SCARD key
获取一个 set 的基数,即 set 中的元素个数
时间复杂度:O(1)
返回值:set 中元素的个数
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4
(integer) 4
127.0.0.1:6379> scard k1
(integer) 4
spop
从set 中删除并返回—个或者多个元素。注意,由于 set 内的元素是无序的,所以取出哪个元素实际是
未定义行为,即可以看作随机的
命令格式:
SPOP key [count]
时间复杂度:O(N) n表示count
返回值:取出的元素
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4 5 6
(integer) 6
# 默认删除一个随机元素
127.0.0.1:6379> spop k1
"5"
# 删除两个随机元素
127.0.0.1:6379> spop k1 2
1) "4"
2) "6"
# 剩余元素
127.0.0.1:6379> smembers k1
1) "1"
2) "2"
3) "3"
smove
将一个元素从源 set 取出并放到目的 set 中
命令格式:
SMOVE source destination member
时间复杂度:O(1)
返回值:1表示移动成功,0表示移动失败
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4
(integer) 4
# 依次将 k1 中的所有元素移动到 k2 中
127.0.0.1:6379> smove k1 k2 3
(integer) 1
127.0.0.1:6379> smove k1 k2 4
(integer) 1
127.0.0.1:6379> smove k1 k2 2
(integer) 1
127.0.0.1:6379> smove k1 k2 1
(integer) 1
# k1 中的元素都被移动到 k2 中
127.0.0.1:6379> smembers k1
(empty array)
127.0.0.1:6379> smembers k2
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sadd k1 1
(integer) 1
127.0.0.1:6379> smove k1 k2 1
(integer) 1
# 移动相同的元素到 k2 中虽然移动成功,但因为 k2 中已经有该元素了,故 k2 无变化
127.0.0.1:6379> smembers k2
1) "1"
2) "2"
3) "3"
4) "4"
srem
该指令用于将指定元素从 set 中删除
命令格式:
SREM key member [member ...]
时间复杂度:O(N),N 表示要删除的元素个数
返回值:本次操作被删除的元素个数
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> srem k1 1 2 3
(integer) 3
127.0.0.1:6379> srem k1 4 4 4
(integer) 1
127.0.0.1:6379> srem k1 6 7
(integer) 0
集合间操作
sinter
该指令用于获取给定 set 的交集中的元素
命令格式:
SINTER key [key ...]
时间复杂度:O(N * M), N 是最小的集合元素个数. M 是集合个数.
返回值:交集的元素
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4
(integer) 4
127.0.0.1:6379> sadd k2 2 3 4 5
(integer) 4
127.0.0.1:6379> sadd k3 3 4 5 6
(integer) 4
127.0.0.1:6379> sinter k1 k2 k3
1) "3"
2) "4"
下面是针对该时间复杂度的讨论:
Redis 的 SINTER 指令用于计算多个集合的交集,其时间复杂度为 O(N * M),其中:
- N 是最小集合的基数(元素个数)
- M 是参与计算的集合数量
原因分析
遍历最小集合:
Redis 会优先选择元素最少的集合作为基准(假设为集合 A),遍历其中的每个元素。这一步的时间复杂度为 O(N)(N 是集合 A 的元素数量)。
检查其他集合:
对于集合 A 中的每个元素,需要检查它是否存在于其他 M-1 个集合中。每次检查的时间复杂度为 O(1)(Redis 的集合底层是哈希表,支持常数时间查找)。
因此,总检查次数为 N × (M-1) ≈ O(N * M)。最坏情况:
当所有集合大小相近时,时间复杂度趋近于 O(N * M)。若某个集合为空,则直接返回空结果(O(1))。
示例
假设有三个集合:
key1 = {a, b, c}(N=3)key2 = {a, c, d, e}key3 = {a, c, f}(M=3)
Redis 会遍历最小的 key1(3 个元素),并对每个元素检查是否同时在 key2 和 key3 中,总操作次数为 3 × 2 = 6 次。
sinterstore
该指令用于获取给定 set 的交集中的元素并保存到目标 set 中
指令格式:
SINTERSTORE destination key [key ...]
时间复杂度:O(N * M), N 是最小的集合元素个数. M 是集合个数.
返回值:交集的元素个数
使用示例:
127.0.0.1:6379> sadd k1 1 2 3 4
(integer) 4
127.0.0.1:6379> sadd k2 2 3 4 5
(integer) 4
127.0.0.1:6379> sadd k3 3 4 5 6
(integer) 4
127.0.0.1:6379> sinterstore k4 k1 k2 k3
(integer) 2
127.0.0.1:6379> smembers k4
1) "3"
2) "4"
sunion、sunionstore、sdiff、sdiffstore
这四个命令和上面的 sinter 和 sinterstore 指令类似,union 用于求并集,diff 用于求差集,故不再赘述
set 相关指令汇总表
| 命令 | 时间复杂度 |
|---|---|
| sadd key element [element …] | O(k),k 是元素个数 |
| srem key element [element …] | O(k),k 是元素个数 |
| scard key | O(1) |
| sismember key element | O(1) |
| srandmember key [count] | O(n),n 是 count |
| spop key [count] | O(n),n 是 count |
| smembers key | O(k),k 是元素个数 |
| sinter key [key …] / sinterstore | O(m * k),k 是几个集合中元素最小的个数,m 是键个数 |
| sunion key [key …] / sunionstore | O(k),k 是多个集合的元素个数总和 |
| sdiff key [key …] / sdiffstore | O(k),k 是多个集合的元素个数总和 |
内部编码
2.5.4 内部编码
集合类型的内部编码有两种:
- intset(整数集合): 当集合中的元素都是整数并且元素的个数小于
set-max-intset-entries配置(默认 512 个)时,Redis 会选用intset来作为集合的内部实现,从而减少内存的使用。 - hashtable(哈希表): 当集合类型无法满足
intset的条件时,Redis 会使用hashtable作为集合的内部实现。
示例
- 当元素个数较少并且都为整数时,内部编码为
intset:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
- 当元素个数超过 512 个,内部编码为
hashtable:
127.0.0.1:6379> sadd setkey 1 2 3 4 ... 513
(integer) 513
127.0.0.1:6379> object encoding setkey
"hashtable"
- 当存在元素不是整数时,内部编码为
hashtable:
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"
应用场景
集合类型在标签系统中的应用
集合类型在标签(tag)系统中有着典型的使用场景,例如:
- 用户兴趣标签(娱乐、体育、历史、新闻等)
- 基于标签的用户分析(共同兴趣、个性化推荐)
- 电子商务的个性化产品推荐
实现标签功能的Redis命令演示
1. 给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
2. 给标签添加用户
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
3. 删除用户下的标签
srem user:1:tags tag1 tag5
4. 删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
5. 计算用户的共同兴趣标签
sinter user:1:tags user:2:tags
应用价值
这种实现方式可以:
- 发现喜欢同一标签的用户群体
- 分析用户的共同兴趣标签
- 为个性化推荐系统提供数据支持
- 增强用户体验和用户黏度