Redis數(shù)據(jù)結(jié)構(gòu):高頻面試題及解析

概述
Redis 是速度非常快的非關(guān)系型(NoSQL)內(nèi)存鍵值數(shù)據(jù)庫,可以存儲鍵和五種不同類型的值之間的映射。
鍵的類型只能為字符串,值支持五種數(shù)據(jù)類型:字符串、列表、集合、散列表、有序集合。
Redis 支持很多特性,例如將內(nèi)存中的數(shù)據(jù)持久化到硬盤中,使用復(fù)制來擴展讀性能,使用分片來擴展寫性能。
數(shù)據(jù)類型
數(shù)據(jù)類型 可以存儲的值 操作
STRING 字符串、整數(shù)或者浮點數(shù) 對整個字符串或者字符串的其中一部分執(zhí)行操作,對整數(shù)和浮點數(shù)執(zhí)行自增或者自減操作
LIST 列表 從兩端壓入或者彈出元素,對單個或者多個元素進行修剪,只保留一個范圍內(nèi)的元素
SET 無序集合 添加、獲取、移除單個元素,檢查一個元素是否存在于集合中,計算交集、并集、差集,從集合里面隨機獲取元素
HASH 包含鍵值對的無序散列表 添加、獲取、移除單個鍵值對,獲取所有鍵值對,檢查某個鍵是否存在
ZSET 有序集合 添加、獲取、刪除元素,根據(jù)分值范圍或者成員來獲取元素,計算一個鍵的排名
STRING
Redis 的 String 類型使用 SDS(簡單動態(tài)字符串)作為底層的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)。SDS 與 C 字符串有所不同,它不僅可以保存文本數(shù)據(jù),還可以保存二進制數(shù)據(jù)。這是因為 SDS 使用 len 屬性的值而不是空字符來判斷字符串是否結(jié)束,并且 SDS 的所有 API 都會以處理二進制的方式來處理 SDS 存放在 buf[] 數(shù)組里的數(shù)據(jù)。因此,SDS 不僅能存放文本數(shù)據(jù),還能保存圖片、音頻、視頻、壓縮文件等二進制數(shù)據(jù)。
另外,Redis 的 SDS API 是安全的,拼接字符串不會造成緩沖區(qū)溢出。這是因為 SDS 在拼接字符串之前會檢查 SDS 空間是否滿足要求,如果空間不夠會自動擴容,從而避免了緩沖區(qū)溢出的問題。
此外,獲取字符串長度的時間復(fù)雜度是 O(1),因為 SDS 結(jié)構(gòu)里用 len 屬性記錄了字符串長度,所以獲取長度的復(fù)雜度為 O(1)。相比之下,C 語言的字符串并不記錄自身長度,所以獲取長度的復(fù)雜度為 O(n)。這些特性使得 SDS 成為 Redis 的一個重要組成部分。
> set hello world
OK
> get hello
"world"
> del hello
(integer) 1
> get hello
(nil)
LIST
Redis 的 List 類型底層數(shù)據(jù)結(jié)構(gòu)可以由雙向鏈表或壓縮列表實現(xiàn)。如果列表元素個數(shù)小于 512 個且每個元素的值都小于 64 字節(jié),則 Redis 會使用壓縮列表作為底層數(shù)據(jù)結(jié)構(gòu);否則,Redis 會使用雙向鏈表作為底層數(shù)據(jù)結(jié)構(gòu)。然而,在 Redis 3.2 版本之后,List 類型底層數(shù)據(jù)結(jié)構(gòu)只由 quicklist 實現(xiàn),代替了雙向鏈表和壓縮列表。
> rpush list-key item
(integer) 1
> rpush list-key item2
(integer) 2
> rpush list-key item
(integer) 3
> lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"
> lindex list-key 1
"item2"
> lpop list-key
"item"
> lrange list-key 0 -1
1) "item2"
2) "item"
SET
Set 類型的底層數(shù)據(jù)結(jié)構(gòu)可以是哈希表或整數(shù)集合。當(dāng)集合中的元素都是整數(shù)并且元素個數(shù)小于512時,Redis使用整數(shù)集合作為Set類型的底層數(shù)據(jù)結(jié)構(gòu);否則,Redis使用哈希表作為Set類型的底層數(shù)據(jù)結(jié)構(gòu)。
> sadd set-key item
(integer) 1
> sadd set-key item2
(integer) 1
> sadd set-key item3
(integer) 1
> sadd set-key item
(integer) 0
> smembers set-key
1) "item"
2) "item2"
3) "item3"
> sismember set-key item4
(integer) 0
> sismember set-key item
(integer) 1
> srem set-key item2
(integer) 1
> srem set-key item2
(integer) 0
> smembers set-key
1) "item"
2) "item3"
HASH
Redis 中的 Hash 類型的底層數(shù)據(jù)結(jié)構(gòu)可以是壓縮列表或哈希表。如果元素個數(shù)小于 512 個且每個元素的值都小于 64 字節(jié),Redis 會使用壓縮列表作為底層數(shù)據(jù)結(jié)構(gòu);否則會使用哈希表。在 Redis 7.0 中,壓縮列表已經(jīng)廢棄,改用 listpack 數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)。
> hset hash-key sub-key1 value1
(integer) 1
> hset hash-key sub-key2 value2
(integer) 1
> hset hash-key sub-key1 value1
(integer) 0
> hgetall hash-key
1) "sub-key1"
2) "value1"
3) "sub-key2"
4) "value2"
> hdel hash-key sub-key2
(integer) 1
> hdel hash-key sub-key2
(integer) 0
> hget hash-key sub-key1
"value1"
> hgetall hash-key
1) "sub-key1"
2) "value1"
ZSET
Zset 類型的底層數(shù)據(jù)結(jié)構(gòu)可以是壓縮列表或跳表。
如果有序集合的元素個數(shù)小于 128 個,且每個元素的值小于 64 字節(jié),則 Redis 會使用壓縮列表作為 Zset 類型的底層數(shù)據(jù)結(jié)構(gòu)。
如果有序集合的元素個數(shù)大于等于 128 個或者每個元素的值大于等于 64 字節(jié),則 Redis 會使用跳表作為 Zset 類型的底層數(shù)據(jù)結(jié)構(gòu)。
需要注意的是,Redis 7.0 中廢棄了壓縮列表數(shù)據(jù)結(jié)構(gòu),改用 listpack 數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)。
> zadd zset-key 728 member1
(integer) 1
> zadd zset-key 982 member0
(integer) 1
> zadd zset-key 982 member0
(integer) 0
> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "982"
> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"
> zrem zset-key member1
(integer) 1
> zrem zset-key member1
(integer) 0
> zrange zset-key 0 -1 withscores
1) "member0"
2) "982"