# 书接上回
前一篇文章,我们学习的是 Redis 的数据结构之 hash, 学习了其基本的操作和使用内部数据结构是 hashtable
和 ziplist
,其中 Redis
中的 hashtable
是用 dict
表示的。如果不记得了其内部构成,就再看看看着上篇文章吧。现在我们继续学习下一个数据类型 set
。
# set
简介
Redis
的 set
数据类型表示 一堆不重复值的集合。
Redis
的 set
数据类型有两种编码方式. OBJ_ENCODING_INTSET
和 OBJ_ENCODING_HT
.
-
OBJ_ENCODING_HT
这种编码方式在上一篇文章 07-Redis 的数据类型之 hash 中已经简单的介绍过了。其实现的数据结构为dict
。 -
OBJ_ENCODING_INTSET
, 这种编码方式是我们要新学习的编码方式。 电梯直达
如果你看到这句话,那就说明你是一个特别认真的人。哈哈哈,我们还是先遵循惯例。先学习 set
类型相关的命令。
# set
类型的应用场景
- 社交系统中存储关注信息,点赞信息,利用交并差运算,计算共同好友等业务中。比如
qq
的好友推荐逻辑,就可以使用差集运算。 - 需要去重的业务逻辑中。某一时间端内系统的增长人数。
- 统计访问网站的独立
IP
。
# set
的基本命令
# sadd
- 语法
SADD key member [member ...]
- 解释
set add
将一个或多个 member
元素加入到集合 key
当中,已经存在于集合的 member
元素将被忽略。
假如 key
不存在,则创建一个只包含 member
元素作成员的集合。
当 key
不是集合类型时,返回一个错误。
- 演示
1 | 127.0.0.1:6379> sadd k52 mem1 mem2 |
# smembers
- 语法
SMEMBERS key
- 解释
set members
返回集合 key
中的所有成员。
不存在的 key
被视为空集合
- 演示
1 | # 查询元素, 注意保存是无序的. |
# sismember
- 语法
SISMEMBER key member
- 解释
set is members
判断 member
元素是否集合 key
的成员。
如果 member
元素是集合的成员,返回 1
。 如果 member
元素不是集合的成员,或 key
不存在,返回 0
- 演示
1 | 127.0.0.1:6379> SADD k54 m1 m2 m3 m4 |
# spop
- 语法
SPOP key [count]
- 解释
set pop
移除并返回集合中的 随机一个 元素。
- 演示
1 | 127.0.0.1:6379> SADD k55 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 |
# srandmemeber
- 语法
SRANDMEMBER key [count]
- 解释
set rand member
返回集合中的 随机 count
个元素 (不会删除元素)
如果 count
为正数,且小于集合基数,那么命令返回一个包含 count
个元素的数组,数组中的元素各不相同。如果 count
大于等于集合基数,那么返回整个集合。
如果 count
为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count
的绝对值。
- 演示
1 | 127.0.0.1:6379> SADD k56 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 |
# srem
- 语法
SREM key member [member ...]
- 解释
set remove
移除集合 key
中的一个或多个 member
元素,不存在的 member
元素会被忽略。
当 key
不是集合类型,返回一个错误。
- 演示
1 | 127.0.0.1:6379> SADD k57 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 |
# smove
- 语法
SMOVE source destination member
- 解释
set move
将 member
元素从 source
集合移动到 destination
集合。
SMOVE
是原子性操作。
如果 source
集合不存在或不包含指定的 member
元素,则 SMOVE
命令不执行任何操作,仅返回 0
。否则, member
元素从 source
集合中被移除,并添加到 destination
集合中去。
当 destination
集合已经包含 member
元素时, SMOVE
命令只是简单地将 source
集合中的 member
元素删除。
当 source
或 destination
不是集合类型时,返回一个错误。
- 演示
1 | 127.0.0.1:6379> SADD k58 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 |
# scard
- 语法
SCARD key
- 解释
返回集合 key
的基数 (集合中元素的数量)。
集合的基数。 当 key
不存在时,返回 0
- 演示
1 | 127.0.0.1:6379> SADD k59 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 |
# sinter
- 语法
SINTER key [key ...]
- 解释
set intersection
: set
的交集
返回一个集合的全部成员,该集合是所有给定集合的交集。
不存在的 key
被视为空集。
当给定集合当中有一个空集时,结果也为空集 (根据集合运算定律)。
- 演示
1 | 127.0.0.1:6379> SADD k60_1 m1 m2 m3 m4 m5 |
# sinterstore
- 语法
SINTERSTORE destination key [key ...]
- 解释
set intersection and store
这个命令类似于 SINTER key [key …] 命令,返回集合的交集。但它将结果保存到 destination 集合,而不是简单地返回结果集。
如果 destination 集合已经存在,则将其覆盖。
destination 可以是 key 本身。
- 演示
1 | 127.0.0.1:6379> SADD k61_1 m1 m2 m3 m4 m5 |
# sunion
- 语法
SUNION key [key ...]
- 解释
set union
返回一个集合的全部成员,如果是多个集合 (key), 返回所有给定集合的并集。
不存在的 key 被视为空集。
- 演示
1 | 127.0.0.1:6379> SADD k62_1 m1 m2 m3 |
# sunionstore
- 语法
SUNIONSTORE destination key [key ...]
- 解释
set union and store
同 SINTERSTORE , 只不过存储的是并集的结果。 将多个集合的并集存储到 distination 中。
- 演示
1 | 127.0.0.1:6379> SADD k63_1 m1 m2 m3 |
# sdiff
- 语法
SDIFF key [key ...]
- 解释
set difference
如果指定一个集合, key
,返回一个集合的全部成员,
如果指定了多个集合 ( key
), 则返回 所有给定集合之间的差集。
不存在的 key
被视为空集。
- 演示
1 | 127.0.0.1:6379> SADD k64_1 m1 m2 m3 |
# sdiffstore
- 语法
SDIFFSTORE destination key [key ...]
- 解释
将集合的差集存储到 destination
集合中.
- 演示
1 | 127.0.0.1:6379> SADD k65_1 m1 m2 m3 |
# sscan
- 语法
SSCAN key cursor [MATCH pattern] [COUNT count]
- 解释
set scan
这是一个查询命令。 同 SCAN
命令。可以参考这篇文章 010 - 其他命令
SCAN
命令是一个基于游标的迭代器( cursor based iterator
): SCAN
命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN
命令的游标参数, 以此来延续之前的迭代过程。
- 演示
1 | 127.0.0.1:6379> SSCAN k66 1 |
# set
的内部结构
在 t_set.c 这个文件中。
1 | robj *setTypeCreate(sds value) { |
表明, set
数据类型是由两种数据结构来实现的。
而在 createSetObject()
,指明了其编码方式是 OBJ_ENCODING_HT
, 即哈希表的方式,也就是使用 dict 这种数据结构来存储的。
1 | robj *createSetObject(void) { |
# hashtable
这里就不赘述了。直接上穿梭机吧。
# intset
在 createIntsetObject()
中指明了使用的编码方式是 OBJ_ENCODING_INTSET
. 如下。
1 | robj *createIntsetObject(void) { |
我们来看看 intset
到底什么何方利器.
我直接全项目搜索: intset
,就找到了 intset.h
.
1 | typedef struct intset { |
# 字段解释:
encoding
: 数据编码,表示intset
中的每个数据元素用几个字节来存储。它有三种可能的取值:INTSET_ENC_INT16
表示每个元素用2
个字节存储,INTSET_ENC_INT32
表示每个元素用4
个字节存储,INTSET_ENC_INT64
表示每个元素用8
个字节存储。因此,intset
中存储的整数最多只能占用64bit
。length
: 表示intset
中的元素个数。encoding
和length
两个字段构成了intset
的头部(header
)。contents
: 是一个柔性数组(flexible array member
),表示intset
的header
后面紧跟着数据元素。这个数组的总长度(即总字节数)等于encoding * length
。柔性数组在Redis
的很多数据结构的定义中都出现过(例如sds
,quicklist
,skiplist
),用于表达一个偏移量。contents
需要单独为其分配空间,这部分内存不包含在intset
结构当中。
这里有个问题.
Redis 是如何决定一个 set 使用哪种编码方式的呢?
set 的编码是由第一个元素决定的。
1 | robj *setTypeCreate(sds value) { |
如果 value
可以转换成 long long
类型的话,就使用 inset
编码方式。
通过看源码发现:
当 intset
的元素个数超过 set_max_intset_entries
这个配置的时候,就会从 intset
编码 ( OBJ_ENCODING_INTSET
) 转换成 ht
编码 ( OBJ_ENCODING_HT
)。
这个我们会在后续文章中说明这里的方案。
好了,关于 set 类型的介绍就到这里了。
# 总结
set
这种类型是一种无重复元素的集合。set
的业务场景关键字:去重,交并差运算。但是一定是无序的。如果要求有序的话,那就 下一篇文章 zset ~set
的15
个命令,务必熟记!!!set
的内部编码方式。哈希表编码和intset
编码。后面会有 关于intset
数据结构的详细介绍的文章~
# 最后
期望与你一起遇见更好的自己