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 數據結構的詳細介紹, 敬請期待吧。最新更新在公衆號裏哦。

最後

希望和你成爲朋友!我們一起學習~
最新文章盡在公衆號【方家小白】,期待和你相逢在【方家小白】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章