目錄
String | list | hash | set | zset | |
應用場景 | 存儲key-value鍵值對 | 朋友圈點贊列表評論列表、消息隊列 | 購物車、存儲對象 | 好友/粉絲/關注的集合、首頁隨機展示 | 排行榜、存學生成績 |
Redis基礎數據結構有哪些?
一、String(字符串)
在任何一種編程語言裏,字符串 String 都是最基礎的數據結構, 那你有想過 Redis 中存儲一個字符串都進行了哪些操作嘛?
在 Redis 中 String 是可以修改的,稱爲 動態字符串 ( Simple Dynamic String 簡稱 SDS )( 快拿小本本記名詞,要考的 ),說是字符串但它的內部結構更像是一個 ArrayList ,內部維護着一個字節數組,並且在其內部預分配了一定的空間,以減少內存的頻繁分配。
Redis 的內存分配機制是這樣:
-
當字符串的長度小於 1MB時,每次擴容都是加倍現有的空間。
-
如果字符串長度超過 1MB時,每次擴容時只會擴展 1MB 的空間。
這樣既保證了內存空間夠用,還不至於造成內存的浪費, 字符串最大長度爲 512MB . 。
image.png
上圖就是字符串的基本結構,其中 content 裏面保存的是字符串內容, 0x\0 作爲結束字符不會被計算 len 中。
分析一下字符串的數據結構
struct SDS{
T capacity; //數組容量
T len; //實際長度
byte flages; //標誌位,低三位表示類型
byte[] content; //數組內容
}
capacity 和 len 兩個屬性都是泛型,爲什麼不直接用 int類型 ?因爲 Redis 內部有很多優化方案,爲更合理的使用內存,不同長度的字符串採用不同的數據類型表示,且在創建字符串的時候 len 會和 capacity 一樣大,不產生冗餘的空間,所以 String 值可以是字符串、數字(整數、浮點數) 或者 二進制。
1、應用場景:
存儲key-value鍵值對,這個比較簡單不細說了
2、字符串(String)常用的命令:
set [key] [value] 給指定key設置值(set 可覆蓋老的值)
get [key] 獲取指定key 的值
del [key] 刪除指定key
exists [key] 判斷是否存在指定key
mset [key1] [value1] [key2] [value2] ...... 批量存鍵值對
mget [key1] [key2] ...... 批量取key
expire [key] [time] 給指定key 設置過期時間 單位秒
setex [key] [time] [value] 等價於 set + expire 命令組合
setnx [key] [value] 如果key不存在則set 創建,否則返回0
incr [key] 如果value爲整數 可用 incr命令每次自增1
incrby [key] [number] 使用incrby命令對整數值 進行增加 number
二、list(列表)
Redis 中的 list 和 Java 中的 LinkedList 很像,底層都是一種鏈表結構, list 的插入和刪除操作非常快,時間複雜度爲 0(1),不像數組結構插入、刪除操作需要移動數據。
像歸像,但是 redis 中的 list 底層可不是一個雙向鏈表那麼簡單。
當數據量較少的時候它的底層存儲結構爲一塊連續內存,稱之爲 ziplist(壓縮列表) ,它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存;當數據量較多的時候將會變成 quicklist(快速鏈表) 結構。
可單純的鏈表也是有缺陷的,鏈表的前後指針 prev 和 next 會佔用較多的內存,會比較浪費空間,而且會加重內存的碎片化。在redis 3.2之後就都改用 ziplist+鏈表 的混合結構,稱之爲 quicklist(快速鏈表) 。
下面具體介紹下兩種鏈表
ziplist(壓縮列表)
先看一下 ziplist 的數據結構,
struct ziplist<T>{ int32 zlbytes; //壓縮列表佔用字節數 int32 zltail_offset; //最後一個元素距離起始位置的偏移量,用於快速定位到最後一個節點 int16 zllength; //元素個數 T[] entries; //元素內容 int8 zlend; //結束位 0xFF }
int32 zlbytes : 壓縮列表佔用字節數 int32 zltail_offset : 最後一個元素距離起始位置的偏移量,用於快速定位到最後一個節點 int16 zllength :元素個數 T[] entries :元素內容 int8 zlend :結束位 0xFF
壓縮列表爲了支持雙向遍歷,所以纔會有 ztail_offset 這個字段,用來快速定位到最後一 個元素,然後倒着遍歷
image.png
entry 的數據結構:
struct entry{ int<var> prevlen; //前一個 entry 的長度 int<var> encoding; //元素類型編碼 optional byte[] content; //元素內容 }
entry 它的 prevlen 字段表示前一個 entry 的字節長度,當壓縮列表倒着遍歷時,需要通過這 個字段來快速定位到下一個元素的位置。
1、應用場景:
由於list它是一個按照插入順序排序的列表,所以應用場景相對還較多的,例如:
-
消息隊列: lpop 和 rpush (或者反過來, lpush 和 rpop )能實現隊列的功能
-
朋友圈的點贊列表、評論列表、排行榜: lpush 命令和 lrange 命令能實現最新列表的功能,每次通過 lpush 命令往列表裏插入新的元素,然後通過 lrange 命令讀取最新的元素列表。
2、list操作的常用命名:
rpush [key] [value1] [value2] ...... 鏈表右側插入 rpop [key] 移除右側列表頭元素,並返回該元素 lpop [key] 移除左側列表頭元素,並返回該元素 llen [key] 返回該列表的元素個數 lrem [key] [count] [value] 刪除列表中與value相等的元素,count是刪除的個數。 count>0 表示從左側開始查找,刪除count個元素,count<0 表示從右側開始查找,刪除count個相同元素,count=0 表示刪除全部相同的元素 (PS: index 代表元素下標,index 可以爲負數, index= 表示倒數第一個元素,同理 index=-2 表示倒數第二 個元素。) lindex [key] [index] 獲取list指定下標的元素 (需要遍歷,時間複雜度爲O(n)) lrange [key] [start_index] [end_index] 獲取list 區間內的所有元素 (時間複雜度爲 O(n)) ltrim [key] [start_index] [end_index] 保留區間內的元素,其他元素刪除(時間複雜度爲 O(n))
三、hash (字典)
Redis 中的 Hash 和 Java的 HashMap 更加相似,都是 數組+鏈表 的結構,當發生 hash 碰撞時將會把元素追加到鏈表上,值得注意的是在 Redis 的 Hash 中 value 只能是字符串.
hset books java "Effective java" (integer) 1 hset books golang "concurrency in go" (integer) 1 hget books java "Effective java" hset user age 17 (integer) 1 hincrby user age 1 #單個 key 可以進行計數 和 incr 命令基本一致 (integer) 18
Hash 和 String 都可以用來存儲用戶信息 ,但不同的是 Hash 可以對用戶信息的每個字段單獨存儲; String 存的是用戶全部信息經過序列化後的字符串,如果想要修改某個用戶字段必須將用戶信息字符串全部查詢出來,解析成相應的用戶信息對象,修改完後在序列化成字符串存入。而 hash可以只對某個字段修改,從而節約網絡流量,不過hash內存佔用要大於 String ,這是 hash 的缺點。
1、應用場景:
- 購物車: hset [key] [field] [value] 命令, 可以實現以 用戶Id , 商品Id 爲 field ,商品數量爲 value ,恰好構成了購物車的3個要素。
- 存儲對象: hash 類型的 (key, field, value) 的結構與對象的 (對象id, 屬性, 值) 的結構相似,也可以用來存儲對象。
2、hash常用的操作命令:
hset [key] [field] [value] 新建字段信息
hget [key] [field] 獲取字段信息
hdel [key] [field] 刪除字段
hlen [key] 保存的字段個數
hgetall [key] 獲取指定key 字典裏的所有字段和值 (字段信息過多,會導致慢查詢 慎用:親身經歷 曾經用過這個這個指令導致線上服務故障)
hmset [key] [field1] [value1] [field2] [value2] ...... 批量創建
hincr [key] [field] 對字段值自增
hincrby [key] [field] [number] 對字段值增加number
四、set(集合)
Redis 中的 set 和 Java 中的 HashSet 有些類似,它內部的鍵值對是無序的、唯一 的。它的內部實現相當於一個特殊的字典,字典中所有的value都是一個值 NULL。當集合中最後一個元素被移除之後,數據結構被自動刪除,內存被回收。
1、應用場景:
- 好友、關注、粉絲、感興趣的人集合:
sinter sismember scard smove
- 首頁展示隨機:美團首頁有很多推薦商家,但是並不能全部展示,set類型適合存放所有需要展示的內容,而 srandmember 命令則可以從中隨機獲取幾個。
- 存儲某活動中中獎的用戶ID ,因爲有去重功能,可以保證同一個用戶不會中獎兩次。
2、set的常用命令:
sadd [key] [value] 向指定key的set中添加元素 smembers [key] 獲取指定key 集合中的所有元素 sismember [key] [value] 判斷集合中是否存在某個value scard [key] 獲取集合的長度 spop [key] 彈出一個元素 srem [key] [value] 刪除指定元素
五、zset(有序集合)
zset 也叫 SortedSet 一方面它是個 set ,保證了內部 value 的唯一性,另方面它可以給每個 value 賦予一個 score ,代表這個value的排序權重。它的內部實現用的是一種叫作“ 跳躍列表 ”的數據結構。
1、應用場景:
zset 可以用做排行榜,但是和 list 不同的是 zset 它能夠實現動態的排序,例如: 可以用來存儲粉絲列表,value 值是粉絲的用戶 ID,score 是關注時間,我們可以對粉絲列表按關注時間進行排序。
zset 還可以用來存儲學生的成績, value 值是學生的 ID, score 是他的考試成績。 我們對成績按分數進行排序就可以得到他的名次。
2、zset有序集合的常用操作命令:
zadd [key] [score] [value] 向指定key的集合中增加元素
zrange [key] [start_index] [end_index] 獲取下標範圍內的元素列表,按score 排序輸出
zrevrange [key] [start_index] [end_index] 獲取範圍內的元素列表 ,按score排序 逆序輸出
zcard [key] 獲取集合列表的元素個數
zrank [key] [value] 獲取元素再集合中的排名
zrangebyscore [key] [score1] [score2] 輸出score範圍內的元素列表
zrem [key] [value] 刪除元素
zscore [key] [value] 獲取元素的score
總結
本文很多概念都一帶而過了,只是給大家粗略的講述一下Redis五種基礎數據結構和應用場景,旨在給小夥伴們一個面試備題的方向
作者:程序員內點事