Redis 5 種基本數據結構(String、List、Hash、Set、Sorted Set)詳解 | JavaGuide

首發於:Redis 5 種基本數據結構詳解 – JavaGuide

相關文章:Redis常見面試題總結(上)

Redis 5 種基本數據結構(String、List、Hash、Set、Sorted Set)在面試中經常會被問到,這篇文章我們一起來回顧溫習一下。

還有幾種比較特殊的數據結構(HyperLogLogs、Bitmap 、Geospatial、Stream)也非常重要,我們後面下次再聊!

下面是正文。

你可以在 Redis 官網上找到 Redis 數據結構非常詳細的介紹:

未來隨著 Redis 新版本的發布,可能會有新的數據結構出現,通過查閱 Redis 官網對應的介紹,你總能獲取到最靠譜的資訊。

String(字元串)

介紹

String 是 Redis 中最簡單同時也是最常用的一個數據結構。

String 是一種二進位安全的數據結構,可以用來存儲任何類型的數據比如字元串、整數、浮點數、圖片(圖片的 base64 編碼或者解碼或者圖片的路徑)、序列化後的對象。

雖然 Redis 是用 C 語言寫的,但是 Redis 並沒有使用 C 的字元串表示,而是自己構建了一種 簡單動態字元串(Simple Dynamic String,SDS)。相比於 C 的原生字元串,Redis 的 SDS 不光可以保存文本數據還可以保存二進位數據,並且獲取字元串長度複雜度為 O(1)(C 字元串為 O(N)),除此之外,Redis 的 SDS API 是安全的,不會造成緩衝區溢出。

常用命令

命令 介紹
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 中儲存的數字值增一
DECR key 將 key 中儲存的數字值減一
EXISTS key 判斷指定 key 是否存在
DEL key(通用) 刪除指定的 key
EXPIRE key seconds(通用) 給指定 key 設置過期時間

更多 Redis String 命令以及詳細使用指南,請查看 Redis 官網對應的介紹://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"

計數器(字元串的內容為整數的時候可以使用):

> SET number 1
OK
> INCR number # 將 key 中儲存的數字值增一
(integer) 2
> GET number
"2"
> DECR number # 將 key 中儲存的數字值減一
(integer) 1
> GET number
"1"

設置過期時間(默認為永不過期)

> EXPIRE key 60
(integer) 1
> SETNX key 60 value # 設置值並設置過期時間
OK
> TTL key
(integer) 56

應用場景

需要存儲常規數據的場景

  • 舉例 :快取 session、token、圖片地址、序列化後的對象(相比較於 Hash 存儲更節省記憶體)。
  • 相關命令 : SETGET

需要計數的場景

  • 舉例 :用戶單位時間的請求數(簡單限流可以用到)、頁面單位時間的訪問數。
  • 相關命令 :SETGETINCRDECR

分散式鎖

利用 SETNX key value 命令可以實現一個最簡易的分散式鎖(存在一些缺陷,通常不建議這樣實現分散式鎖)。

List(列表)

介紹

Redis 中的 List 其實就是鏈表數據結構的實現。我在 線性數據結構 :數組、鏈表、棧、隊列 這篇文章中詳細介紹了鏈表這種數據結構,我這裡就不多做介紹了。

許多高級程式語言都內置了鏈表的實現比如 Java 中的 LinkedList,但是 C 語言並沒有實現鏈表,所以 Redis 實現了自己的鏈表數據結構。Redis 的 List 的實現為一個 雙向鏈表,即可以支援反向查找和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷。

常用命令

命令 介紹
RPUSH key value1 value2 … 在指定列表的尾部(右邊)添加一個或多個元素
LPUSH key value1 value2 … 在指定列表的頭部(左邊)添加一個或多個元素
LSET key index value 將指定列表索引 index 位置的值設置為 value
LPOP key 移除並獲取指定列表的第一個元素(最左邊)
RPOP key 移除並獲取指定列表的最後一個元素(最右邊)
LLEN key 獲取列表元素數量
LRANGE key start end 獲取列表 start 和 end 之間 的元素

更多 Redis List 命令以及詳細使用指南,請查看 Redis 官網對應的介紹://redis.io/commands/?group=list

通過 RPUSH/LPOP 或者 LPUSH/RPOP實現隊列

> 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 實現棧

> 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 實現分頁查詢,性能非常高!

通過 LLEN 查看鏈表長度

> LLEN myList
(integer) 3

應用場景

資訊流展示

  • 舉例 :最新文章、最新動態。
  • 相關命令 : LPUSHLRANGE

消息隊列

Redis List 數據結構可以用來做消息隊列,只是功能過於簡單且存在很多缺陷,不建議這樣做。

相對來說,Redis 5.0 新增加的一個數據結構 Stream 更適合做消息隊列一些,只是功能依然非常簡陋。和專業的消息隊列相比,還是有很多欠缺的地方比如消息丟失和堆積問題不好解決。

Hash(哈希)

介紹

Redis 中的 Hash 是一個 String 類型的 field-value(鍵值對) 的映射表,特別適合用於存儲對象,後續操作的時候,你可以直接修改這個對象中的某些欄位的值。

Hash 類似於 JDK1.8 前的 HashMap,內部實現也差不多(數組 + 鏈表)。不過,Redis 的 Hash 做了更多優化。

常用命令

命令 介紹
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 獲取指定哈希表中欄位的數量

更多 Redis Hash 命令以及詳細使用指南,請查看 Redis 官網對應的介紹://redis.io/commands/?group=hash

模擬對象數據存儲

> 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"

應用場景

對象數據存儲場景

  • 舉例 :用戶資訊、商品資訊、文章資訊、購物車資訊。
  • 相關命令 :HSET (設置單個欄位的值)、HMSET(設置多個欄位的值)、HGET(獲取單個欄位的值)、HMGET(獲取多個欄位的值)。

Set(集合)

介紹

Redis 中的 Set 類型是一種無序集合,集合中的元素沒有先後順序但都唯一,有點類似於 Java 中的 HashSet 。當你需要存儲一個列表數據,又不希望出現重複數據時,Set 是一個很好的選擇,並且 Set 提供了判斷某個元素是否在一個 Set 集合內的重要介面,這個也是 List 所不能提供的。

你可以基於 Set 輕易實現交集、並集、差集的操作,比如你可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。這樣的話,Set 可以非常方便的實現如共同關注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程。

常用命令

命令 介紹
SADD key member1 member2 … 向指定集合添加一個或多個元素
SMEMBERS key 獲取指定集合中的所有元素
SCARD key 獲取指定集合的元素數量
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 隨機獲取指定集合中指定數量的元素

更多 Redis Set 命令以及詳細使用指南,請查看 Redis 官網對應的介紹://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 : value1value2
  • mySet2value2value3

求交集

> 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"

應用場景

需要存放的數據不能重複的場景

  • 舉例:網站 UV 統計(數據量巨大的場景還是 HyperLogLog更適合一些)、文章點贊、動態點贊等場景。
  • 相關命令:SCARD(獲取集合數量) 。

需要獲取多個數據源交集、並集和差集的場景

  • 舉例 :共同好友(交集)、共同粉絲(交集)、共同關注(交集)、好友推薦(差集)、音樂推薦(差集) 、訂閱號推薦(差集+交集) 等場景。
  • 相關命令:SINTER(交集)、SINTERSTORE (交集)、SUNION (並集)、SUNIONSTORE(並集)、SDIFF(交集)、SDIFFSTORE (交集)。

需要隨機獲取數據源中的元素的場景

  • 舉例 :抽獎系統、隨機。
  • 相關命令:SPOP(隨機獲取集合中的元素並移除,適合不允許重複中獎的場景)、SRANDMEMBER(隨機獲取集合中的元素,適合允許重複中獎的場景)。

Sorted Set(有序集合)

介紹

Sorted Set 類似於 Set,但和 Set 相比,Sorted Set 增加了一個權重參數 score,使得集合中的元素能夠按 score 進行有序排列,還可以通過 score 的範圍來獲取元素的列表。有點像是 Java 中 HashMapTreeSet 的結合體。

常用命令

命令 介紹
ZADD key score1 member1 score2 member2 … 向指定有序集合添加一個或多個元素
ZCARD KEY 獲取指定有序集合的元素數量
ZSCORE key member 獲取指定有序集合中指定元素的 score 值
ZINTERSTORE destination numkeys key1 key2 … 將給定所有有序集合的交集存儲在 destination 中,對相同元素對應的 score 值進行 SUM 聚合操作,numkeys 為集合數量
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 官網對應的介紹://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) 。
  • myZset2value2 (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

應用場景

需要隨機獲取數據源中的元素根據某個權重進行排序的場景

  • 舉例 :各種排行榜比如直播間送禮物的排行榜、朋友圈的微信步數排行榜、王者榮耀中的段位排行榜、話題熱度排行榜等等。
  • 相關命令 :ZRANGE (從小到大排序) 、 ZREVRANGE (從大到小排序)、ZREVRANK (指定元素排名)。

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

需要存儲的數據有優先順序或者重要程度的場景 比如優先順序任務隊列。

  • 舉例 :優先順序任務隊列。
  • 相關命令 :ZRANGE (從小到大排序) 、 ZREVRANGE (從大到小排序)、ZREVRANK (指定元素排名)。

參考

後記

專註 Java 原創乾貨分享,大三開源 JavaGuide (「Java學習+面試指南」一份涵蓋大部分 Java 程式設計師所需要掌握的核心知識。準備 Java 面試,首選 JavaGuide!),目前已經 120k+ Star。

原創不易,歡迎點贊分享,歡迎關注我在部落格園的帳號,我會持續分享原創乾貨!加油,沖!

如果本文對你有幫助的話,歡迎點贊分享,這對我繼續分享&創作優質文章非常重要。感謝 🙏🏻