# 书接上回
前一篇文章,我们学习的是 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数据结构的详细介绍的文章~
# 最后
期望与你一起遇见更好的自己
