Redis的数据类型之集合 · set

悠悠 2023-02-16 01:50 95阅读 0赞

文章目录

    • 书接上回
    • `set`简介
    • `set`类型的应用场景
    • `set`的基本命令
      • `sadd`
      • `smembers`
      • `sismember`
      • `spop`
      • `srandmemeber`
      • `srem`
      • `smove`
      • `scard`
      • `sinter`
      • `sinterstore`
      • `sunion`
      • `sunionstore`
      • `sdiff`
      • `sdiffstore`
      • `sscan`
    • `set`的内部结构
      • `hashtable`
      • intset
        • 字段解释:
    • 总结
      • 最后

书接上回

前一篇文章,我们学习的是 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 这个文件中。

  1. robj *setTypeCreate(sds value) {
  2. if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
  3. return createIntsetObject();
  4. return createSetObject();
  5. }

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

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

  1. robj *createSetObject(void) {
  2. dict *d = dictCreate(&setDictType, NULL);
  3. robj *o = createObject(OBJ_SET, d);
  4. o->encoding = OBJ_ENCODING_HT;
  5. return o;
  6. }

hashtable

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

intset

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

  1. robj *createIntsetObject(void) {
  2. intset *is = intsetNew();
  3. robj *o = createObject(OBJ_SET, is);
  4. o->encoding = OBJ_ENCODING_INTSET;
  5. return o;
  6. }

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

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

  1. typedef struct intset {
  2. uint32_t encoding;
  3. uint32_t length;
  4. int8_t contents[];
  5. } 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 的编码是由第一个元素决定的。

  1. robj *setTypeCreate(sds value) {
  2. if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
  3. return createIntsetObject();
  4. return createSetObject();
  5. }
  6. int isSdsRepresentableAsLongLong(sds s, long long *llval) {
  7. return string2ll(s, sdslen(s), llval) ? C_OK : C_ERR;
  8. }

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

通过看源码发现:

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

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

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

总结

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

最后

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

发表评论

表情:
评论列表 (有 0 条评论,95人围观)

还没有评论,来说两句吧...

相关阅读

    相关 redis数据类型set

    set(集合):其实就是数学中的集合,集合中元素要具备三个特性:确定性、无序性、互异性。 确定性:一个元素要么属于集合,要么不属于集合。 无序性:集合中每个元素的