美團二面: Redis 5 種基礎數(shù)據(jù)結(jié)構(gòu)?
??《Java 面試指北》來啦!這是一份教你如何更高效地準備面試的小冊,涵蓋常見八股文(系統(tǒng)設計、常見框架、分布式、高并發(fā) ......)、優(yōu)質(zhì)面經(jīng)等內(nèi)容。
你好,我是 Guide。Redis 5 種基本數(shù)據(jù)結(jié)構(gòu)(String、List、Hash、Set、Sorted Set)在面試中經(jīng)常會被問到,這篇文章我們一起來回顧溫習一下。
還有幾種比較特殊的數(shù)據(jù)結(jié)構(gòu)(HyperLogLogs、Bitmap 、Geospatial、Stream)也非常重要,我們后面下次再聊!

下面是正文。
你可以在 Redis 官網(wǎng)上找到 Redis 數(shù)據(jù)結(jié)構(gòu)非常詳細的介紹:
Redis Data Structures[1] Redis Data types tutorial[2]
未來隨著 Redis 新版本的發(fā)布,可能會有新的數(shù)據(jù)結(jié)構(gòu)出現(xiàn),通過查閱 Redis 官網(wǎng)對應的介紹,你總能獲取到最靠譜的信息。

String(字符串)
介紹
String 是 Redis 中最簡單同時也是最常用的一個數(shù)據(jù)結(jié)構(gòu)。
String 是一種二進制安全的數(shù)據(jù)結(jié)構(gòu),可以用來存儲任何類型的數(shù)據(jù)比如字符串、整數(shù)、浮點數(shù)、圖片(圖片的 base64 編碼或者解碼或者圖片的路徑)、序列化后的對象。

雖然 Redis 是用 C 語言寫的,但是 Redis 并沒有使用 C 的字符串表示,而是自己構(gòu)建了一種 簡單動態(tài)字符串(Simple Dynamic String,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本數(shù)據(jù)還可以保存二進制數(shù)據(jù),并且獲取字符串長度復雜度為 O(1)(C 字符串為 O(N)),除此之外,Redis 的 SDS API 是安全的,不會造成緩沖區(qū)溢出。
常用命令
| 命令 | 介紹 |
|---|---|
| SET key value | 設置指定 key 的值 |
| SETNX key value | 只有在 key 不存在時設置 key 的值 |
| GET key | 獲取指定 key 的值 |
| MSET key1 value1 key2 value2 … | 設置一個或多個指定 key 的值 |
| MGET key1 key2 ... | 獲取一個或多個指定 key 的值 |
| STRLEN key | 返回 key 所儲存的字符串值的長度 |
| INCR key | 將 key 中儲存的數(shù)字值增一 |
| DECR key | 將 key 中儲存的數(shù)字值減一 |
| EXISTS key | 判斷指定 key 是否存在 |
| DEL key(通用) | 刪除指定的 key |
| EXPIRE key seconds(通用) | 給指定 key 設置過期時間 |
更多 Redis String 命令以及詳細使用指南,請查看 Redis 官網(wǎng)對應的介紹:https://redis.io/commands/?group=string 。
基本操作 :
> SET key value
OK
> GET key
"value"
> EXISTS key
(integer) 1
> STRLEN key
(integer) 5
> DEL key
(integer) 1
> GET key
(nil)
批量設置 :
> MSET key1 value1 key2 value2
OK
> MGET key1 key2 # 批量獲取多個 key 對應的 value
1) "value1"
2) "value2"
計數(shù)器(字符串的內(nèi)容為整數(shù)的時候可以使用):
> SET number 1
OK
> INCR number # 將 key 中儲存的數(shù)字值增一
(integer) 2
> GET number
"2"
> DECR number # 將 key 中儲存的數(shù)字值減一
(integer) 1
> GET number
"1"
設置過期時間(默認為永不過期):
> EXPIRE key 60
(integer) 1
> SETNX key 60 value # 設置值并設置過期時間
OK
> TTL key
(integer) 56
應用場景
需要存儲常規(guī)數(shù)據(jù)的場景
舉例 :緩存 session、token、圖片地址、序列化后的對象(相比較于 Hash 存儲更節(jié)省內(nèi)存)。 相關(guān)命令 : SET、GET。
需要計數(shù)的場景
舉例 :用戶單位時間的請求數(shù)(簡單限流可以用到)、頁面單位時間的訪問數(shù)。 相關(guān)命令 : SET、GET、INCR、DECR。
分布式鎖
利用 SETNX key value 命令可以實現(xiàn)一個最簡易的分布式鎖(存在一些缺陷,通常不建議這樣實現(xiàn)分布式鎖)。
List(列表)
介紹
Redis 中的 List 其實就是鏈表數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)。我在 線性數(shù)據(jù)結(jié)構(gòu) :數(shù)組、鏈表、棧、隊列[3] 這篇文章中詳細介紹了鏈表這種數(shù)據(jù)結(jié)構(gòu),我這里就不多做介紹了。
許多高級編程語言都內(nèi)置了鏈表的實現(xiàn)比如 Java 中的 LinkedList,但是 C 語言并沒有實現(xiàn)鏈表,所以 Redis 實現(xiàn)了自己的鏈表數(shù)據(jù)結(jié)構(gòu)。Redis 的 List 的實現(xiàn)為一個 雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷。

常用命令
| 命令 | 介紹 |
|---|---|
| RPUSH key value1 value2 ... | 在指定列表的尾部(右邊)添加一個或多個元素 |
| LPUSH key value1 value2 ... | 在指定列表的頭部(左邊)添加一個或多個元素 |
| LSET key index value | 將指定列表索引 index 位置的值設置為 value |
| LPOP key | 移除并獲取指定列表的第一個元素(最左邊) |
| RPOP key | 移除并獲取指定列表的最后一個元素(最右邊) |
| LLEN key | 獲取列表元素數(shù)量 |
| LRANGE key start end | 獲取列表 start 和 end 之間 的元素 |
更多 Redis List 命令以及詳細使用指南,請查看 Redis 官網(wǎng)對應的介紹:https://redis.io/commands/?group=list 。
通過 RPUSH/LPOP 或者 LPUSH/RPOP實現(xiàn)隊列 :
> RPUSH myList value1
(integer) 1
> RPUSH myList value2 value3
(integer) 3
> LPOP myList
"value1"
> LRANGE myList 0 1
1) "value2"
2) "value3"
> LRANGE myList 0 -1
1) "value2"
2) "value3"
通過 RPUSH/RPOP或者LPUSH/LPOP 實現(xiàn)棧 :
> RPUSH myList2 value1 value2 value3
(integer) 3
> RPOP myList2 # 將 list的頭部(最右邊)元素取出
"value3"
我專門畫了一個圖方便大家理解 RPUSH , LPOP , lpush , RPOP 命令:

通過 LRANGE 查看對應下標范圍的列表元素 :
> RPUSH myList value1 value2 value3
(integer) 3
> LRANGE myList 0 1
1) "value1"
2) "value2"
> LRANGE myList 0 -1
1) "value1"
2) "value2"
3) "value3"
通過 LRANGE 命令,你可以基于 List 實現(xiàn)分頁查詢,性能非常高!
通過 LLEN 查看鏈表長度 :
> LLEN myList
(integer) 3
應用場景
信息流展示
舉例 :最新文章、最新動態(tài)。 相關(guān)命令 : LPUSH、LRANGE。
消息隊列
Redis List 數(shù)據(jù)結(jié)構(gòu)可以用來做消息隊列,只是功能過于簡單且存在很多缺陷,不建議這樣做。
相對來說,Redis 5.0 新增加的一個數(shù)據(jù)結(jié)構(gòu) Stream 更適合做消息隊列一些,只是功能依然非常簡陋。和專業(yè)的消息隊列相比,還是有很多欠缺的地方比如消息丟失和堆積問題不好解決。
Hash(哈希)
介紹
Redis 中的 Hash 是一個 String 類型的 field-value(鍵值對) 的映射表,特別適合用于存儲對象,后續(xù)操作的時候,你可以直接修改這個對象中的某些字段的值。
Hash 類似于 JDK1.8 前的 HashMap,內(nèi)部實現(xiàn)也差不多(數(shù)組 + 鏈表)。不過,Redis 的 Hash 做了更多優(yōu)化。

常用命令
| 命令 | 介紹 |
|---|---|
| HSET key field value | 設置指定哈希表中指定字段的值 |
| HSETNX key field value | 只有指定字段不存在時設置指定字段的值 |
| HMSET key field1 value1 field2 value2 ... | 同時將一個或多個 field-value (域-值)對設置到指定哈希表中 |
| HGET key field | 獲取指定哈希表中指定字段的值 |
| HMGET key field1 field2 ... | 獲取指定哈希表中一個或者多個指定字段的值 |
| HGETALL key | 獲取指定哈希表中所有的鍵值對 |
| HEXISTS key field | 查看指定哈希表中指定的字段是否存在 |
| HDEL key field1 field2 ... | 刪除一個或多個哈希表字段 |
| HLEN key | 獲取指定哈希表中字段的數(shù)量 |
更多 Redis Hash 命令以及詳細使用指南,請查看 Redis 官網(wǎng)對應的介紹:https://redis.io/commands/?group=hash 。
模擬對象數(shù)據(jù)存儲 :
> HMSET userInfoKey name "guide" description "dev" age "24"
OK
> HEXISTS userInfoKey name # 查看 key 對應的 value中指定的字段是否存在。
(integer) 1
> HGET userInfoKey name # 獲取存儲在哈希表中指定字段的值。
"guide"
> HGET userInfoKey age
"24"
> HGETALL userInfoKey # 獲取在哈希表中指定 key 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
> HSET userInfoKey name "GuideGeGe"
> HGET userInfoKey name
"GuideGeGe"
應用場景
對象數(shù)據(jù)存儲場景
舉例 :用戶信息、商品信息、文章信息、購物車信息。 相關(guān)命令 : HSET(設置單個字段的值)、HMSET(設置多個字段的值)、HGET(獲取單個字段的值)、HMGET(獲取多個字段的值)。
Set(集合)
介紹
Redis 中的 Set 類型是一種無序集合,集合中的元素沒有先后順序但都唯一,有點類似于 Java 中的 HashSet 。當你需要存儲一個列表數(shù)據(jù),又不希望出現(xiàn)重復數(shù)據(jù)時,Set 是一個很好的選擇,并且 Set 提供了判斷某個元素是否在一個 Set 集合內(nèi)的重要接口,這個也是 List 所不能提供的。
你可以基于 Set 輕易實現(xiàn)交集、并集、差集的操作,比如你可以將一個用戶所有的關(guān)注人存在一個集合中,將其所有粉絲存在一個集合。這樣的話,Set 可以非常方便的實現(xiàn)如共同關(guān)注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程。

常用命令
| 命令 | 介紹 |
|---|---|
| SADD key member1 member2 ... | 向指定集合添加一個或多個元素 |
| SMEMBERS key | 獲取指定集合中的所有元素 |
| SCARD key | 獲取指定集合的元素數(shù)量 |
| SISMEMBER key member | 判斷指定元素是否在指定集合中 |
| SINTER key1 key2 ... | 獲取給定所有集合的交集 |
| SINTERSTORE destination key1 key2 ... | 將給定所有集合的交集存儲在 destination 中 |
| SUNION key1 key2 ... | 獲取給定所有集合的并集 |
| SUNIONSTORE destination key1 key2 ... | 將給定所有集合的并集存儲在 destination 中 |
| SDIFF key1 key2 ... | 獲取給定所有集合的差集 |
| SDIFFSTORE destination key1 key2 ... | 將給定所有集合的差集存儲在 destination 中 |
| SPOP key count | 隨機移除并獲取指定集合中一個或多個元素 |
| SRANDMEMBER key count | 隨機獲取指定集合中指定數(shù)量的元素 |
更多 Redis Set 命令以及詳細使用指南,請查看 Redis 官網(wǎng)對應的介紹:https://redis.io/commands/?group=set 。
基本操作 :
> SADD mySet value1 value2
(integer) 2
> SADD mySet value1 # 不允許有重復元素,因此添加失敗
(integer) 0
> SMEMBERS mySet
1) "value1"
2) "value2"
> SCARD mySet
(integer) 2
> SISMEMBER mySet value1
(integer) 1
> SADD mySet2 value2 value3
(integer) 2
mySet:value1、value2。mySet2:value2、value3。
求交集 :
> SINTERSTORE mySet3 mySet mySet2
(integer) 1
> SMEMBERS mySet3
1) "value2"
求并集 :
> SUNION mySet mySet2
1) "value3"
2) "value2"
3) "value1"
求差集 :
> SDIFF mySet mySet2 # 差集是由所有屬于 mySet 但不屬于 A 的元素組成的集合
1) "value1"
應用場景
需要存放的數(shù)據(jù)不能重復的場景
舉例:網(wǎng)站 UV 統(tǒng)計(數(shù)據(jù)量巨大的場景還是 HyperLogLog更適合一些)、文章點贊、動態(tài)點贊等場景。相關(guān)命令: SCARD(獲取集合數(shù)量) 。

需要獲取多個數(shù)據(jù)源交集、并集和差集的場景
舉例 :共同好友(交集)、共同粉絲(交集)、共同關(guān)注(交集)、好友推薦(差集)、音樂推薦(差集) 、訂閱號推薦(差集+交集) 等場景。 相關(guān)命令: SINTER(交集)、SINTERSTORE(交集)、SUNION(并集)、SUNIONSTORE(并集)、SDIFF(交集)、SDIFFSTORE(交集)。

需要隨機獲取數(shù)據(jù)源中的元素的場景
舉例 :抽獎系統(tǒng)、隨機。 相關(guān)命令: SPOP(隨機獲取集合中的元素并移除,適合不允許重復中獎的場景)、SRANDMEMBER(隨機獲取集合中的元素,適合允許重復中獎的場景)。
Sorted Set(有序集合)
介紹
Sorted Set 類似于 Set,但和 Set 相比,Sorted Set 增加了一個權(quán)重參數(shù) score,使得集合中的元素能夠按 score 進行有序排列,還可以通過 score 的范圍來獲取元素的列表。有點像是 Java 中 HashMap 和 TreeSet 的結(jié)合體。

常用命令
| 命令 | 介紹 |
|---|---|
| ZADD key score1 member1 score2 member2 ... | 向指定有序集合添加一個或多個元素 |
| ZCARD KEY | 獲取指定有序集合的元素數(shù)量 |
| ZSCORE key member | 獲取指定有序集合中指定元素的 score 值 |
| ZINTERSTORE destination numkeys key1 key2 ... | 將給定所有有序集合的交集存儲在 destination 中,對相同元素對應的 score 值進行 SUM 聚合操作,numkeys 為集合數(shù)量 |
| ZUNIONSTORE destination numkeys key1 key2 ... | 求并集,其它和 ZINTERSTORE 類似 |
| ZDIFF destination numkeys key1 key2 ... | 求差集,其它和 ZINTERSTORE 類似 |
| ZRANGE key start end | 獲取指定有序集合 start 和 end 之間的元素(score 從低到高) |
| ZREVRANGE key start end | 獲取指定有序集合 start 和 end 之間的元素(score 從高到底) |
| ZREVRANK key member | 獲取指定有序集合中指定元素的排名(score 從大到小排序) |
更多 Redis Sorted Set 命令以及詳細使用指南,請查看 Redis 官網(wǎng)對應的介紹:https://redis.io/commands/?group=sorted-set 。
基本操作 :
> ZADD myZset 2.0 value1 1.0 value2
(integer) 2
> ZCARD myZset
2
> ZSCORE myZset value1
2.0
> ZRANGE myZset 0 1
1) "value2"
2) "value1"
> ZREVRANGE myZset 0 1
1) "value1"
2) "value2"
> ZADD myZset2 4.0 value2 3.0 value3
(integer) 2
myZset:value1(2.0)、value2(1.0) 。myZset2:value2(4.0)、value3(3.0) 。
獲取指定元素的排名 :
> ZREVRANK myZset value1
0
> ZREVRANK myZset value2
1
求交集 :
> ZINTERSTORE myZset3 2 myZset myZset2
1
> ZRANGE myZset3 0 1 WITHSCORES
value2
5
求并集 :
> ZUNIONSTORE myZset4 2 myZset myZset2
3
> ZRANGE myZset4 0 2 WITHSCORES
value1
2
value3
3
value2
5
求差集 :
> ZDIFF 2 myZset myZset2 WITHSCORES
value1
2
應用場景
需要隨機獲取數(shù)據(jù)源中的元素根據(jù)某個權(quán)重進行排序的場景
舉例 :各種排行榜比如直播間送禮物的排行榜、朋友圈的微信步數(shù)排行榜、王者榮耀中的段位排行榜、話題熱度排行榜等等。 相關(guān)命令 : ZRANGE(從小到大排序) 、ZREVRANGE(從大到小排序)、ZREVRANK(指定元素排名)。

《Java 面試指北》 的「技術(shù)面試題篇」就有一篇文章詳細介紹如何使用 Sorted Set 來設計制作一個排行榜。

需要存儲的數(shù)據(jù)有優(yōu)先級或者重要程度的場景 比如優(yōu)先級任務隊列。
舉例 :優(yōu)先級任務隊列。 相關(guān)命令 : ZRANGE(從小到大排序) 、ZREVRANGE(從大到小排序)、ZREVRANK(指定元素排名)。
參考資料
Redis Data Structures: https://redis.com/redis-enterprise/data-structures/
[2]Redis Data types tutorial: https://redis.io/docs/manual/data-types/data-types-tutorial/
[3]線性數(shù)據(jù)結(jié)構(gòu) :數(shù)組、鏈表、棧、隊列: https://javaguide.cn/cs-basics/data-structure/linear-data-structure.html
·········· END ··············
近期文章精選 :
走近作者 :
??《Java 面試指北》來啦!這是一份教你如何更高效地準備面試的小冊,涵蓋常見八股文(系統(tǒng)設計、常見框架、分布式、高并發(fā) ......)、優(yōu)質(zhì)面經(jīng)等內(nèi)容。
??如果本文對你有幫助的話,歡迎 點贊&在看&分享 ,這對我繼續(xù)分享&創(chuàng)作優(yōu)質(zhì)文章非常重要。非常感謝!
