Redis的数据类型之集合 · set

书接上回

前一篇文章,我们学习的是 Redis的数据结构之hash, 学习了其基本的操作和使用内部数据结构是hashtableziplist,其中Redis 中的hash是用 dict 表示的。如果不记得了其内部构成, 就再看看看着上篇文章吧。现在我们继续学习下一个数据类型 set

set简介

Redisset 数据类型表示 一堆不重复值的集合。

Redisset 数据类型有两种编码方式. OBJ_ENCODING_INTSETOBJ_ENCODING_HT.

  • OBJ_ENCODING_HT这种编码方式在上一篇文章 07-Redis的数据类型之hash.md中已经简单的介绍过了。其实现的数据结构为 dict。更加深入的分享, 穿梭机.

  • OBJ_ENCODING_INTSET,这种编码方式是我们要新学习的编码方式。 电梯直达

如果你看到这句话, 那就说明你是一个特别认真的人。哈哈哈,我们还是先遵循惯例。先学习set类型相关的命令。

set类型的应用场景

  • 社交系统中存储关注信息,点赞信息,利用交并差运算,计算共同好友等业务中。比如qq的好友推荐逻辑,就可以使用差集运算。
  • 需要去重的业务逻辑中。某一时间端内系统的增长人数。
  • 统计访问网站的独立IP

set的基本命令

sadd

  • 语法

SADD key member [member ...]

  • 解释

set add

将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。

假如 key 不存在,则创建一个只包含 member 元素作成员的集合。

key 不是集合类型时,返回一个错误。

  • 演示
127.0.0.1:6379> sadd k52 mem1 mem2 
(integer) 2
127.0.0.1:6379> sadd k52 mem1 
(integer) 0
127.0.0.1:6379> sadd k52 mem1 mem3
(integer) 1

smembers

  • 语法

SMEMBERS key

  • 解释

set members

返回集合 key 中的所有成员。

不存在的 key 被视为空集合

  • 演示
# 查询元素, 注意保存是无序的.
127.0.0.1:6379> SADD k53 m1 m2 m3 m4 m5
(integer) 5
127.0.0.1:6379> SMEMBERS k53
1) "m4"
2) "m3"
3) "m2"
4) "m1"
5) "m5"

sismember

  • 语法

SISMEMBER key member

  • 解释

set is members

判断 member 元素是否集合 key 的成员。

如果 member 元素是集合的成员,返回 1 。 如果 member 元素不是集合的成员,或 key 不存在,返回 0

  • 演示
127.0.0.1:6379> SADD k54 m1 m2 m3 m4
(integer) 4
127.0.0.1:6379> SISMEMBER k54 m2
(integer) 1
127.0.0.1:6379> SISMEMBER k54 m5
(integer) 0

spop

  • 语法

SPOP key [count]

  • 解释

set pop

移除并返回集合中的 随机一个 元素。

  • 演示
127.0.0.1:6379> SADD k55 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10
(integer) 10
# 随机移除一个元素
127.0.0.1:6379> spop k55
"m9"
127.0.0.1:6379> spop k55
"m2"
127.0.0.1:6379> spop k55
"m3"
# 随机移除3个元素
127.0.0.1:6379> spop k55 3
1) "m5"
2) "m4"
3) "m10"
# 查看所有元素
127.0.0.1:6379> SMEMBERS k55
1) "m1"
2) "m7"
3) "m8"
4) "m6"

srandmemeber

  • 语法

SRANDMEMBER key [count]

  • 解释

set rand member

返回集合中的 随机 count 个元素(不会删除元素)

如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。

如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。

  • 演示
127.0.0.1:6379> SADD k56 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10
(integer) 10
# 随机返回一个元素
127.0.0.1:6379> SRANDMEMBER k56 
"m1"
127.0.0.1:6379> SRANDMEMBER k56 
"m9"
127.0.0.1:6379> SRANDMEMBER k56 
"m5"
# count是正数,小于集合的元素数,返回count个元素,无重复元素
127.0.0.1:6379> SRANDMEMBER k56 5
1) "m7"
2) "m1"
3) "m5"
4) "m6"
5) "m3"
# count是正数,大于集合的元素数,返回整个集合
127.0.0.1:6379> SRANDMEMBER k56 20
 1) "m5"
 2) "m6"
 3) "m4"
 4) "m8"
 5) "m7"
 6) "m1"
 7) "m10"
 8) "m3"
 9) "m2"
10) "m9"
# count为负数, 返回20个集合中的元素,元素会重复
127.0.0.1:6379> SRANDMEMBER k56 -20
 1) "m6"
 2) "m6"
 3) "m9"
 4) "m7"
 5) "m6"
 6) "m5"
 7) "m6"
 8) "m9"
 9) "m1"
10) "m6"
11) "m1"
12) "m9"
13) "m2"
14) "m1"
15) "m2"
16) "m5"
17) "m9"
18) "m2"
19) "m4"
20) "m6"

srem

  • 语法

SREM key member [member ...]

  • 解释

set remove

移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。

key 不是集合类型,返回一个错误。

  • 演示
127.0.0.1:6379> SADD k57 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10
(integer) 10
127.0.0.1:6379> SREM k57 m1 m2 m3 
(integer) 3
127.0.0.1:6379> SMEMBERS k57
1) "m7"
2) "m8"
3) "m5"
4) "m6"
5) "m4"
6) "m10"
7) "m9"
127.0.0.1:6379> SREM k57 m11 m12
(integer) 0

smove

  • 语法

SMOVE source destination member

  • 解释

set move

member 元素从 source 集合移动到 destination 集合。

SMOVE 是原子性操作

如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。

destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。

sourcedestination 不是集合类型时,返回一个错误。

  • 演示
127.0.0.1:6379> SADD k58 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10
(integer) 10
# 移动两个元素到k58_dis
127.0.0.1:6379> SMOVE k58 k58_dis m1
(integer) 1
127.0.0.1:6379> SMOVE k58 k58_dis m2
(integer) 1
127.0.0.1:6379> SMOVE k58 k58_dis m1
(integer) 0
# k58中的m1,m2 已被移除。
127.0.0.1:6379> SMEMBERS k58
1) "m7"
2) "m8"
3) "m5"
4) "m6"
5) "m4"
6) "m10"
7) "m3"
8) "m9"
# k58_dis中的m1,m2
127.0.0.1:6379> SMEMBERS k58_dis
1) "m2"
2) "m1"

scard

  • 语法

SCARD key

  • 解释

返回集合 key 的基数(集合中元素的数量)。
集合的基数。 当 key 不存在时,返回 0

  • 演示
127.0.0.1:6379> SADD k59 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10
(integer) 10
# 获取元素个数
127.0.0.1:6379> SCARD k59
(integer) 10

sinter

  • 语法

SINTER key [key ...]

  • 解释

set intersection : set 的交集

返回一个集合的全部成员,该集合是所有给定集合的交集。

不存在的 key 被视为空集。

当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)。

  • 演示
127.0.0.1:6379> SADD k60_1 m1 m2 m3 m4 m5 
(integer) 5
127.0.0.1:6379> SADD k60_2 m2 m3 m4 m5 m6
(integer) 5
127.0.0.1:6379> SADD k60_3 m4 m5 m6 m7 m8
(integer) 5
# 指定了一个key,返回集合的所有元素
127.0.0.1:6379> SINTER k60_1
1) "m4"
2) "m3"
3) "m2"
4) "m1"
5) "m5"
# 多个key的时候,返回集合的交集。
127.0.0.1:6379> SINTER k60_1 k60_2
1) "m4"
2) "m3"
3) "m2"
4) "m5"
# 多个key的时候,返回集合的交集。
127.0.0.1:6379> SINTER k60_1 k60_2 k60_3
1) "m4"
2) "m5"
# k60_4不存在,为空集
127.0.0.1:6379> SINTER k60_1 k60_4
(empty list or set)

sinterstore

  • 语法

SINTERSTORE destination key [key ...]

  • 解释

set intersection and store

这个命令类似于 SINTER key [key …] 命令,返回集合的交集。但它将结果保存到 destination 集合,而不是简单地返回结果集。

如果 destination 集合已经存在,则将其覆盖。

destination 可以是 key 本身。

  • 演示
127.0.0.1:6379> SADD k61_1 m1 m2 m3 m4 m5
(integer) 5
127.0.0.1:6379> SADD k61_2 m4 m5 m6 m7 m8
(integer) 5
# 将k61_1 和 k61_2 集合的交集存储到k61_dis中
127.0.0.1:6379> SINTERSTORE k61_dis k61_1 k61_2
(integer) 2
# 查看 k61_dis
127.0.0.1:6379> SMEMBERS k61_dis
1) "m4"
2) "m5"
# k61_1 和 k61_2 没有变化
127.0.0.1:6379> SMEMBERS k61_1
1) "m4"
2) "m3"
3) "m2"
4) "m1"
5) "m5"
127.0.0.1:6379> SMEMBERS k61_2
1) "m7"
2) "m8"
3) "m5"
4) "m6"
5) "m4"
# 如果目标集合(k61_dis)存在,元素会被覆盖掉。
127.0.0.1:6379> SADD k61_3 m1 m2 m3 
(integer) 3
127.0.0.1:6379> SINTERSTORE k61_dis k61_1 k61_3
(integer) 3
127.0.0.1:6379> SMEMBERS k61_dis
1) "m1"
2) "m2"
3) "m3"

sunion

  • 语法

SUNION key [key ...]

  • 解释

set union

返回一个集合的全部成员,如果是多个集合(key),返回所有给定集合的并集。

不存在的 key 被视为空集。

  • 演示
127.0.0.1:6379> SADD k62_1 m1 m2 m3 
(integer) 3
127.0.0.1:6379> SADD k62_2 m2 m3 m4 m5 m6 
(integer) 5
# 一个key,返回整个集合。
127.0.0.1:6379> SUNION k62_1
1) "m1"
2) "m2"
3) "m3"
# 多个key,返回并集
127.0.0.1:6379> SUNION k62_1 k62_2
1) "m3"
2) "m1"
3) "m5"
4) "m6"
5) "m2"
6) "m4"

sunionstore

  • 语法

SUNIONSTORE destination key [key ...]

  • 解释

set union and store

同 SINTERSTORE , 只不过存储的是并集的结果。 将多个集合的并集存储到 distination 中。

  • 演示
127.0.0.1:6379> SADD k63_1 m1 m2 m3 
(integer) 3
127.0.0.1:6379> SADD k63_2 m2 m3 m4 m5 m6 
(integer) 5
127.0.0.1:6379> SUNIONSTORE k63_dis k62_1 k62_2
(integer) 6
127.0.0.1:6379> SMEMBERS k63_dis
1) "m3"
2) "m1"
3) "m5"
4) "m6"
5) "m2"
6) "m4"

sdiff

  • 语法

SDIFF key [key ...]

  • 解释

set difference

如果指定一个集合,key,返回一个集合的全部成员,

如果指定了多个集合(key),则返回 所有给定集合之间的差集。

不存在的 key 被视为空集。

  • 演示
127.0.0.1:6379> SADD k64_1 m1 m2 m3 
(integer) 3
127.0.0.1:6379> SADD k64_2 m2 m3 m4 m5 m6 
(integer) 5
# 返回 k64_1 - k64_2
127.0.0.1:6379> SDIFF k64_1 k64_2
1) "m1"
# 返回 k64_2 - k64_1
127.0.0.1:6379> SDIFF k64_2 k64_1
1) "m6"
2) "m4"
3) "m5"

sdiffstore

  • 语法

SDIFFSTORE destination key [key ...]

  • 解释

将集合的差集存储到 destination 集合中.

  • 演示
127.0.0.1:6379> SADD k65_1 m1 m2 m3 
(integer) 3
127.0.0.1:6379> SADD k65_2 m2 m3 m4 m5 m6 
(integer) 5
127.0.0.1:6379> SDIFFSTORE k65_dis_1 k65_1 k65_2
(integer) 1
127.0.0.1:6379> SMEMBERS k65_dis_1
1) "m1"
127.0.0.1:6379> SDIFFSTORE k65_dis_2 k65_2 k65_1
(integer) 3
127.0.0.1:6379> SMEMBERS k65_dis_2
1) "m6"
2) "m4"
3) "m5"

sscan

  • 语法

SSCAN key cursor [MATCH pattern] [COUNT count]

  • 解释

set scan

这是一个查询命令。 同 SCAN 命令. 可以参考这篇文章 010-其他命令

SCAN 命令是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。

  • 演示
127.0.0.1:6379> SSCAN k66 1
1) "0"
2) 1) "m1"
   2) "m3"
   3) "m2"
   4) "m4"
   5) "m5"
127.0.0.1:6379> SSCAN k66 0
1) "0"
2) 1) "m6"
   2) "m1"
   3) "m3"
   4) "m2"
   5) "m4"
   6) "m5"
127.0.0.1:6379> SSCAN k66 1 MATCH m2 Count 10
1) "0"
2) 1) "m2"
127.0.0.1:6379> 

set的内部结构

在 t_set.c 这个文件中。

robj *setTypeCreate(sds value) {
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
        return createIntsetObject();
    return createSetObject();
}

表明,set 数据类型是由两种数据结构来实现的。

而在 createSetObject(),指明了其编码方式是 OBJ_ENCODING_HT,即哈希表的方式, 也就是使用 dict 这种数据结构来存储的。

robj *createSetObject(void) {
    dict *d = dictCreate(&setDictType, NULL);
    robj *o = createObject(OBJ_SET, d);
    o->encoding = OBJ_ENCODING_HT;
    return o;
}

hashtable

这里就不赘述了。直接上穿梭机吧。

intset

createIntsetObject()中指明了使用的编码方式是 OBJ_ENCODING_INTSET. 如下。

robj *createIntsetObject(void) {
    intset *is = intsetNew();
    robj *o = createObject(OBJ_SET, is);
    o->encoding = OBJ_ENCODING_INTSET;
    return o;
}

我们来看看 intset 到底什么何方利器.

我直接全项目搜索: intset ,就找到了 intset.h.

typedef struct intset {
    uint32_t encoding; 
    uint32_t length;
    int8_t contents[];
} intset;
字段解释:
  • encoding: 数据编码,表示intset中的每个数据元素用几个字节来存储。它有三种可能的取值:INTSET_ENC_INT16表示每个元素用2个字节存储,INTSET_ENC_INT32表示每个元素用4个字节存储,INTSET_ENC_INT64表示每个元素用8个字节存储。因此,intset中存储的整数最多只能占用64bit
  • length: 表示intset中的元素个数。encodinglength两个字段构成了intset的头部(header)。
  • contents: 是一个柔性数组(flexible array member),表示intsetheader后面紧跟着数据元素。这个数组的总长度(即总字节数)等于encoding * length。柔性数组在Redis的很多数据结构的定义中都出现过(例如sds, quicklist, skiplist),用于表达一个偏移量。contents需要单独为其分配空间,这部分内存不包含在intset结构当中。

这里有个问题.

Redis 是如何决定一个set 使用哪种编码方式的呢?

set 的编码是由第一个元素决定的。

robj *setTypeCreate(sds value) {
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
        return createIntsetObject();
    return createSetObject();
}

int isSdsRepresentableAsLongLong(sds s, long long *llval) {
    return string2ll(s, sdslen(s), llval) ? C_OK : C_ERR;
}

如果 value 可以转换成 long long 类型的话,就使用 inset 编码方式。

通过看源码发现:

intset的元素个数超过 set_max_intset_entries这个配置的时候,就会从intset编码(OBJ_ENCODING_INTSET)转换成 ht 编码(OBJ_ENCODING_HT)。

这个我们会在后续文章中说明这里的方案。

好了,关于 set 类型的介绍就到这里了。

总结

  • set这种类型是一种无重复元素的集合。
  • set的业务场景关键字: 去重, 交并差运算。但是一定是无序的。如果要求有序的话,那就请等待下一篇文章吧。
  • set15 个命令, 务必熟记!!!
  • set的内部编码方式。哈希表编码和intset编码。关于 intset 数据结构的详细介绍, 敬请期待吧。最新更新在公众号里哦。

最后

希望和你成为朋友!我们一起学习~
最新文章尽在公众号【方家小白】,期待和你相逢在【方家小白】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方_小_白

谢谢金主子,记得关注方家小白哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值