面試中常常問到的Redis七種數據類型,你都真正瞭解嗎?

前言

Redis不是一個簡單的鍵值對存儲,它其實是一個支持各類類型數據結構的存儲。在傳統的鍵值存儲中,是將字符串鍵關聯到字符串值,可是在Redis中,這些值不只限於簡單的字符串,還能夠支持更復雜的數據結構。下面就是Redis支持的數據結構:redis

  • 字符串(String):二進制安全字符串。
  • 列表(List):根據插入順序排序的字符串元素列表,基於鏈表實現。
  • 集合(Set):惟一的亂序的字符串元素的集合。
  • 有序集合(Sorted Set):與集合相似,可是每一個字符串元素都與一個稱爲score的數字相關聯。元素老是按其score排序,而且能夠檢索必定score範圍的元素。
  • 哈希(Hash):由字段與值相關聯組成的映射,字段和值都是字符串。
  • 位圖(Bitmap):像操做位數組同樣操做字符串值,能夠設置和清除某個位,對全部爲1的位進行計數,找到第一個設置1的位,找到第一個設置0的位等等。
  • HyperLogLogs:一種機率數據結構,使用較小的內存空間來統計惟一元素的數量,偏差小於1%。

鍵(Key)

是二進制安全的,這意味着您可使用任何二進制序列做爲鍵,能夠是OneMoreStudy這樣的字符串,也可使圖片文件的內容,空字符串也是有效的。不過,還有一些其餘規則:算法

  • 不要使用過長的,好比一個1KB的鍵。不只是多佔內存方面的問題,而是在數據集中查找可能須要進行一些耗時的比較。若是真的有比較大的,先對它進行哈希(好比:MD五、SHA1)是一個好主意。
  • 也不要使用太短的,好比:OMS100f,相對於one-more-study:100:fans,後者更具備可讀性。可能會佔用更多內存,可是相對於值所佔的內存,所增長的內存仍是小不少的。咱們要找到一個平衡點,不長也不短。
  • 多個字段以冒號分隔,一個字段內多個單詞以連詞符或點分隔,好比:one-more-study:100:fans,或者one.more.study:100:fans
  • 容許的最大值爲512MB。

字符串(String)

字符串類型是和關聯的最簡單的類型。它是Memcached中惟一的數據類型,所以對於新手來講,在Redis中使用它也是很容易的。是字符串類型,當咱們也使用字符串類型做爲值時,咱們會能夠從一個字符串映射到另外一個字符串。字符串數據類型有不少應用場景,例如緩存HTML片斷或頁面。數組

下面簡單介紹一下字符串的命令(在redis-cli中使用):緩存

> set one-more-key OneMoreStudy
OK
> get one-more-key
"OneMoreStudy"
複製代碼

使用SETGET命令來設置和查詢字符串值的方式。須要注意的是,若是當前已經和字符串值相關聯,SET命令將會替換已存儲在中的現有值。字符串能夠是任意的二進制數據,好比jpeg圖像。字符串最多不能大於512MB。SET命令還有一些實用的可選參數,好比:安全

> set one-more-key Java nx   #若是key存在,則設置失敗。
(nil)
> set one-more-key Java xx   #若是key存在,才設置成功。
OK
複製代碼

雖然字符串是Redis的基本值,但也可使用它們執行一些實用的操做。好比:bash

> set one-more-counter 50
OK
> incr one-more-counter   #自增長1
(integer) 51
> incr one-more-counter   #自增長1
(integer) 52
> incrby one-more-counter 5   #自增長5
(integer) 57
複製代碼

INCR命令將字符串值解析爲整數,將其自增長1,最後將得到的值設置爲新值。還有其餘相似的命令,例如INCRBY,DECR和DECRBY等命令。INCR命令是原子操做,即時有多個客戶端同時同一個key的INCR命令,也不會進入競態條件。好比,上面的例子先設置one-more-counter的值爲50,即便兩個客戶端同時發出INCR命令,那麼最後的值也確定是52。服務器

可使用MSETMGET命令在單個命令中設置或查詢多個的值,對於減小延遲也頗有用。好比:數據結構

> mset a 1 b 2 c 3
OK
> mget a b c
1) "1"
2) "2"
3) "3"
複製代碼

使用MGET命令時,Redis返回一個值的數組。app

使用DEL命令能夠刪除鍵和相關聯的值,存在指定的鍵則返回1,不存在指定的鍵則返回0。使用EXISTS命令判斷Redis中是否存在指定的鍵,存在指定的鍵則返回1,不存在指定的鍵則返回0。好比:性能

> set one-more-key OneMoreStudy
OK
> exists one-more-key
(integer) 1
> del one-more-key
(integer) 1
> exists one-more-key
(integer) 0
複製代碼

使用TYPE命令,能夠返回存儲在指定key的值的數據類型,好比:

> set one-more-key OneMoreStudy
OK
> type one-more-key
string
> del one-more-key
(integer) 1
> type one-more-key
none
複製代碼

在討論更復雜的數據結構以前,咱們須要討論另外一個功能,該功能不管值類型是什麼都適用,它就是EXPIRE命令。它能夠爲設置到期時間,當超過這個到期時間後,該將自動銷燬,就像對這個調用了DEL命令同樣。好比:

> set one-more-key OneMoreStudy
OK
> expire one-more-key 5
(integer) 1
> get one-more-key #馬上調用
"OneMoreStudy"
> get one-more-key #5秒鐘後調用
(nil)
複製代碼

上面的例子,適用了EXPIRE命令設置了過時時間,也可使用PERSIST命令移除的過時時間,這個將持久保持。除了EXPIRE命令,還可使用SET命令設置過時時間,好比:

> set one-more-key OneMoreStudy ex 10 #設置過時時間爲10秒
OK
> ttl one-more-key
(integer) 9
複製代碼

上面的例子,設置了一個字符串值OneMoreStudyone-more-key,該的到期時間爲10秒。以後,調用TTL命令以檢查該的剩餘生存時間。

到期時間可使用秒或毫秒精度進行設置,但到期時間的分辨率始終爲1毫秒。實際上,Redis服務器上存儲的不是到期時間長度,而是該到期的時間。

列表(List)

Redis列表是使用鏈表實現的,這就意味着在頭部或尾部增長或刪除一個的元素的時間複雜度是O(1),很是快的。不過,按索引查詢對應元素的時間複雜度就是O(n),慢不少。若是想快速查詢大量數據,可使用有序集合,後面會有介紹。

LPUSH命令將一個新元素添加到列表的左側(頂部),而RPUSH命令將一個新元素添加到列表的右側(底部)。最後,LRANGE命令能夠從列表中按範圍提取元素。好比:

> rpush one-more-list A
(integer) 1
> rpush one-more-list B
(integer) 2
> lpush one-more-list first
(integer) 3
> lrange one-more-list 0 -1
1) "first"
2) "A"
3) "B"
複製代碼

LRANGE命令須要另外兩個參數,要返回的第一個元素的索引和最後一個元素的索引。若是索引爲負值,Redis將從末尾開始計數,-1是列表的最後一個元素,-2是列表的倒數第二個元素,依此類推。

LPUSHRPUSH命令支持多個參數,可使用一次命令添加多個元素,好比:

> rpush one-more-list 1 2 3 4 5 "last"
(integer) 9
> lrange one-more-list 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "last"
複製代碼

在Redis列表上,也能夠移除並返回元素。與LPUSHRPUSH命令,對應的就是LPOPRPOP命令,LPOP命令是將列表的左側(頂部)的元素移除並返回,RPOP命令是將列表的右側(底部)的元素移除並返回。好比:

> rpush one-more-list a b c
(integer) 3
> rpop one-more-list
"c"
> rpop one-more-list
"b"
> rpop one-more-list
"a"
複製代碼

咱們添加了三個元素,並移除並返回了三個元素,此時列表爲空,沒有任何元素。若是再使用RPOP命令,會返回一個NULL值:

> rpop one-more-list
(nil)
複製代碼

使用RPUSHRPOP命令,或者LPUSHLPOP命令能夠實現棧的功能,使用LPUSHRPOP命令,或者RPUSHLPOP命令能夠實現隊列的功能。也能夠實現生產者和消費者模式,好比多個生產者使用LPUSH命令將任務添加到列表中,多個消費者使用RPOP命令將任務從列表中取出。可是,有時列表可能爲空,沒有任何要處理的任務,所以RPOP命令僅返回NULL。在這種狀況下,消費者被迫等待一段時間,而後使用RPOP命令重試。這就暴露了有幾個缺點:

  1. 客戶端和服務端之間能夠處理無用的命令,由於在列表爲空時的全部請求將沒法完成任何實際工做,它們只會返回NULL
  2. 因爲消費者在收到NULL以後會等待一段時間,所以會增長任務處理的延遲。爲了減少延遲,咱們能夠在兩次調用RPOP之間等待更少的時間,這就擴大了更多對Redis的無用調用。

有什麼辦法能夠解決呢?使用BRPOPBLPOP的命令,它們和RPOPLPOP命令相似,惟一的區別是:若是列表爲空時,命令會被阻塞,直到有新元素添加到列表中,或指定的超時時間到了時,它們纔會返回到調用方。好比:

> brpop tasks 5
複製代碼

它含義是,列表爲空時,等待列表中的元素,但若是5秒鐘後沒有新的元素被添加,則返回。您能夠將超時時間傳入0,表示永遠等待元素添加。也能夠傳入多個列表,這時會按參數前後順序依次檢查各個列表,返回第一個非空列表的尾部元素。另外還有如下3點須要注意的:

  1. 當列表爲空,而且有多個客戶端在等待時,有一個新的元素被添加到列表中,它會被第一個等待的客戶端獲取到,以此類推。
  2. 返回值與RPOP命令相比有所不一樣,它是一個包含兩個元素的數組,包含key和對應的元素,由於BRPOPBLPOP命令可以阻止等待來自多個列表的元素。
  3. 超過了超時時間,會返回NULL

列表的建立和刪除都是由Redis自動完成的,當嘗試向不存在的添加元素時,Redis會自動建立一個空的列表;當最後一個元素被移除時,Redis會自動刪除這個列表。這不是特定於列表的,它適用於由多個元素組成的全部Redis數據類型,好比集合、有序集合、哈希,它們都有3條規則:

  • 當咱們將元素添加到聚合數據類型時,若是目標不存在,則在添加元素以前會建立一個空的聚合數據類型。好比:
> del one-more-list
(integer) 1
> lpush one-more-list 1 2 3
(integer) 3
複製代碼

可是,在存在時,就不能操做錯誤的數據類型了,好比:

> set one-more-key OneMoreStudy
OK
> lpush one-more-key 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> type one-more-key
string
複製代碼
  • 當咱們從聚合數據類型中刪除元素時,若是該值保持爲空,則key將自動銷燬。好比:
> lpush one-more-list 1 2 3
(integer) 3
> exists one-more-list
(integer) 1
> lpop one-more-list
"3"
> lpop one-more-list
"2"
> lpop one-more-list
"1"
> exists one-more-list
(integer) 0
複製代碼
  • 當對應key不存在,而且調用只讀命令(如LLEN命令,獲取列表長度)或寫命令(如LPOP命令)時,都會返回空聚合數據類型的結果。好比:
> del one-more-list
(integer) 0
> llen one-more-list
(integer) 0
> lpop one-more-list
(nil)
複製代碼

Redis爲了追求高性能,列表的內部實現不是一個簡單的鏈表,這裏先賣個關子,後續的文章會詳細介紹。

集合(Set)

集合是一個字符串的無序集合,SADD命令能夠將新元素添加到集合中。還能夠對集合進行許多其餘操做,例如:判斷給定元素是否已存在、執行多個集合之間的交集、並集或差等等。好比:

> sadd one-more-set 1 2 3
(integer) 3
> smembers one-more-set
1) "1"
2) "3"
3) "2"
複製代碼

在上面的例子中,在集合中添加了三個元素,並讓Redis返回全部元素。正如你所見,返回的元素是沒有排序的。在每次調用時,元素的順序都有可能不同。

還可使用SISMEMBER命令判斷給定元素是否已存在,好比:

> sismember one-more-set 3
(integer) 1
> sismember one-more-set 30
(integer) 0
複製代碼

在上面的例子中,3在集合中,因此返回1;而30不在集合中,因此返回0。

可使用SINTER命令,計算出多個集合的交集;使用SUNION命令,計算多個集合的並集;使用SPOP命令,移除並返回集合中的一個隨機元素;使用SCARD命令,計算集合中的元素的數量。好比:

> sadd one-more-set1 1 2 3
(integer) 3
> sadd one-more-set2 2 3 4
(integer) 3
> sinter one-more-set1 one-more-set2 #交集
1) "3"
2) "2"
> sunion one-more-set1 one-more-set2 #並集
1) "1"
2) "3"
3) "2"
4) "4"
> spop one-more-set1 #隨機移除一個元素
"3"
> scard one-more-set1 #元素數量
(integer) 2
複製代碼

有序集合(Sorted Set)

有序集合是一種相似於集合和哈希之間混合的數據類型。像集合同樣,有序集合中由惟一的、非重複的字符串元素組成,所以從某種意義上說,有序集合也是一個集合。可是集合中的元素是沒有排序的,而有序集合中的每一個元素都與一個稱爲分數(score)的浮點值相關聯,這就是爲何有序集合也相似於哈希的緣由,由於每一個元素都映射到一個值。有序集合的排序規則以下:

  • 若是A和B是兩個具備不一樣分數的元素,那麼若是A.分數>B.分數,則A>B。
  • 若是A和B的分數徹底相同,那麼若是A字符串在字典排序上大於B字符串,則A>B。A和B字符串不能相等,由於有序集合中的元素都是惟一的。

咱們來舉個例子,把王者榮耀戰隊的名字和積分添加到有序集合中,其中把戰隊的名字做爲值,把戰隊的積分做爲分數。

> zadd kpl 12 "eStarPro"
(integer) 1
> zadd kpl 12 "QGhappy"
(integer) 1
> zadd kpl 10 "XQ"
(integer) 1
> zadd kpl 8 "EDG.M"
(integer) 1
> zadd kpl 8 "RNG.M"
(integer) 1
> zadd kpl 4 "TES"
(integer) 1
> zadd kpl 2 "VG"
(integer) 1
複製代碼

如上所示,ZADD命令和SADD命令類似,可是多了一個額外的參數(在要添加的元素的前面)做爲分數。ZADD命令也支持多個參數,雖然在上面的例子中未使用它,但你也能夠指定多個分數和值對。使用有序集合,快速地返回按其積分排序的戰隊列表,由於實際上它們已經被排序了。

須要注意的是,爲了快速獲取有序集合中的元素,每次添加元素的時間複雜度都爲O(log(N)),這是由於有序集合是同時使用跳躍表和字典來實現的,具體原理這裏先賣個關子,後續的文章會詳細介紹。

可使用ZRANGE命令按照升序獲取對應的值,好比:

> zrange kpl 0 -1
1) "VG"
2) "TES"
3) "EDG.M"
4) "RNG.M"
5) "XQ"
6) "QGhappy"
7) "eStarPro"
複製代碼

0和-1表明查詢從第一個到最後一個的元素。還可使用ZREVRANGE命令按照降序獲取對應的值,好比:

> zrevrange kpl 0 -1
1) "eStarPro"
2) "QGhappy"
3) "XQ"
4) "RNG.M"
5) "EDG.M"
6) "TES"
7) "VG"
複製代碼

加上WITHSCORES參數,就能夠連同分數一塊兒返回,好比:

> zrange kpl 0 -1 withscores
 1) "VG"
 2) "2"
 3) "TES"
 4) "4"
 5) "EDG.M"
 6) "8"
 7) "RNG.M"
 8) "8"
 9) "XQ"
10) "10"
11) "QGhappy"
12) "12"
13) "eStarPro"
14) "12"
複製代碼

有序集合還有更強大的功能,好比在分數範圍內操做,讓咱們獲取小於10(含)的戰隊,使用ZRANGEBYSCORE命令:

> zrangebyscore kpl -inf 10
1) "VG"
2) "TES"
3) "EDG.M"
4) "RNG.M"
5) "XQ"
複製代碼

這就是獲取分數從負無窮到10所對應的值,一樣的咱們也能夠獲取分數從4到10所對應的值:

> zrangebyscore kpl 4 10
1) "TES"
2) "EDG.M"
3) "RNG.M"
4) "XQ"
複製代碼

另外有用的命令:ZRANK命令,它能夠返回指定值的升序排名(從0開始);ZREVRANK命令,它能夠返回指定值的降序排名(從0開始),好比:

> zrank kpl "EDG.M"
(integer) 2
> zrevrank kpl "EDG.M"
(integer) 4
複製代碼

有序集合的分數是隨時更新的,只要對已有的有序集合調用ZADD命令,就會以O(log(N))時間複雜度更新其分數和排序。這樣,當有大量更新時,有序集合是合適的。因爲這種特性,常見的場景是排行榜,能夠方便地顯示排名前N位的用戶和用戶在排行榜中的排名。

哈希(Hash)

Redis的哈希和人們指望的「哈希」結構是同樣的,它是一個無序哈希,內部存儲了不少鍵值對,好比:

> hmset one-more-fans:100 name Lily age 25
OK
> hget one-more-fans:100 name
"Lily"
> hget one-more-fans:100 age
"25"
> hgetall one-more-fans:100
1) "name"
2) "Lily"
3) "age"
4) "25"
複製代碼

儘管哈希很容易用來表示對象,可是實際上能夠放入哈希中的字段數是沒有實際限制的,所以您能夠以更多種的不一樣方式使用哈希。除了HGET命令獲取單個字段對應的值,也可使用HMSET命令獲取多個字段及對應的值,它返回的是一個數組,好比:

> hmget one-more-fans:100 name age non-existent-field
1) "Lily"
2) "25"
3) (nil)
複製代碼

還可使用HINCRBY命令,爲指定字段的值作增量,好比:

> hget one-more-fans:100 age
"25"
> hincrby one-more-fans:100 age 3
(integer) 28
> hget one-more-fans:100 age
"28"
複製代碼

Redis哈希的實現結構,和Java中的HashMap是同樣的,也是「數組+鏈表」的結構,當發生數組位置碰撞是,就會將碰撞的元素用鏈表串起來。不過Redis爲了追求高性能,rehash的方式不太同樣,這裏先賣個關子,後續的文章會詳細介紹。

位圖(Bitmap)

位圖不是實際的數據類型,而是在String類型上定義的一組面向位的操做。因爲字符串是二進制安全的,而且最大長度爲512MB,所以能夠設置多達2^32個不一樣的位。位圖操做分爲兩類:固定單個位操做,好比將一個位設置爲1或0或獲取其值;對位組的操做,好比計算給定位範圍內設置位的數量。

位圖的最大優勢之一是,它們在存儲信息時一般能夠節省大量空間。例如,在以增量用戶ID位標識表示用戶是否要接收新聞通信,僅使用512 MB內存就能夠記住40億用戶的一位信息。

使用SETBITGETBIT命令來設置和獲取指定位,好比:

> setbit one-more-key 10 1
(integer) 0
> getbit one-more-key 10
(integer) 1
> getbit one-more-key 11
(integer) 0
複製代碼

SETBIT命令將位號做爲其第一個參數,將其設置爲1或0的值做爲其第二個參數。若是位號超出當前字符串長度,該命令將會自動擴大字符串。GETBIT命令只是返回指定位號的位的值,若是位號超出存儲的字符串長度則會返回0。

對位組的操做有如下3個命令:

  1. BITOP命令能夠在不一樣的字符串之間執行按位運算,提供的位運算有與、或、非和異或。
  2. BITCOUNT命令能夠統計指定範圍內位數爲1的個數。
  3. BITPOS命令能夠查找指定範圍內爲0或1的第一位。
> set one-more-key "\x13\x7f" #二進制爲0001 0011 0111 1111
OK
> bitcount one-more-key #整個字符串中1的位數
(integer) 10
> bitcount one-more-key 0 0 #第一個字符(0001 0011)中1的位數
(integer) 3
> bitcount one-more-key 1 1 #第二個字符(0111 1111)中1的位數
(integer) 7
> bitpos one-more-key 0 #整個字符串中第一個0位
(integer) 0
> bitpos one-more-key 1 #整個字符串中第一個1位
(integer) 3
> bitpos one-more-key 1 0 0 #第一個字符(0001 0011)中第一個1位
(integer) 3
> bitpos one-more-key 1 1 1 #第二個字符(0111 1111)中第一個1位
(integer) 9
複製代碼

位圖能夠應用於各種實時分析,也能夠節省空間高效地存儲位信息。好比,記錄用戶天天的簽到數據,每個位表示用戶是否簽到過,這樣就能夠計算出某個時間段用戶簽到了幾回,某個時間段用戶第一次簽到是哪一天。

HyperLogLogs

HyperLogLog是一種機率數據結構,用於統計惟一元素的數量,也能夠理解爲估計集合中元素的個數。

一般狀況下,對惟一元素進行統計數量時,須要使用與要統計的元素數量成比例的內存量,由於須要記住過去已經看到的元素,以免屢次對其進行統計。可是,有一組算法能夠之內存換取精度,最終會獲得帶有標準偏差的估計數量,在Redis的HyperLogLogs中,該偏差小於1%。

這個算法的神奇之處在於,再也不須要使用與所統計元素數量成比例的內存量,而可使用恆定數量的內存。在最壞的狀況下佔據12KB的內存空間,Redis對HyperLogLog的存儲進行了優化,在計數比較少時,佔據的內存空間會更小,這裏先賣個關子,後續的文章會詳細介紹其中原理。

在集合中,能夠將每一個元素添加到集合中,並使用SCARD命令獲取集合中的元素數量,由於SADD命令不會從新添加現有元素,因此元素都是惟一的。HyperLogLog的操做和集合比較相似,使用PFADD命令將元素添加到HyperLogLog中,相似於集合的SADD命令;使用PFCOUNT命令獲取HyperLogLog中的惟一元素的當前近似值數量,相似於集合的SCARD命令。好比:

> pfadd one-more-hll a b c d e
(integer) 1
> pfcount one-more-hll 
(integer) 5
複製代碼

Redis中的HyperLogLog儘管在技術上是不一樣的數據結構,但被編碼爲字符串,所以能夠調用GET命令來序列化HyperLogLog,而後調用SET命令來將其反序列化回服務器。

總結

Redis提供更加豐富的數據結構,(Key)和字符串(String),都是二進制安全的字符串;列表(List),根據插入順序排序的字符串元素列表,基於鏈表實現;集合(Set),惟一的亂序的字符串元素的集合;有序集合(Sorted Set),與集合相似,可是每一個字符串元素都與一個稱爲score的數字相關聯;哈希(Hash),由字段與值相關聯組成的映射,字段和值都是字符串;位圖(Bitmap),像操做位數組同樣操做字符串值,能夠設置和清除某個位,對全部爲1的位進行計數,找到第一個設置1的位,找到第一個設置0的位等等;HyperLogLogs,一種機率數據結構,使用較小的內存空間來統計惟一元素的數量,偏差小於1%。

相關文章
相關標籤/搜索