也當過面試官,面試過不少應聘者,因為是我自己招人自己用,所以我不會看應聘者造火箭的技術有多牛比,只看擰螺絲的手藝瓷不瓷實。畢竟以後是一個整體,拖了大家後腿團隊都很難受。
面試的題目一般也不會太難,就像問Redis
,我只是想確認他真正用過就夠了。Redis
5種基礎資料結構和簡單操作要知道,最基本的要求,如果這個時候他會說出每種資料結構大致的應用場景,那麼這一定是加分的,起碼要比那些只會說出幾種資料結構後,在那乾瞪眼等我問下一個問題的強很多,千萬別冷場。
Redis基礎資料結構有哪些?
一、String(字串)
在任何一種程式語言裡,字串String
都是最基礎的資料結構, 那你有想過Redis
中儲存一個字串都進行了哪些操作嘛?
在Redis
中String
是可以修改的,稱為動態字串
(Simple Dynamic String
簡稱 SDS
)(快拿小本本記名詞,要考的),說是字串但它的內部結構更像是一個 ArrayList
,內部維護著一個位元組陣列,並且在其內部預分配了一定的空間,以減少記憶體的頻繁分配。
Redis
的記憶體分配機制是這樣:
- 當字串的長度小於 1MB時,每次擴容都是加倍現有的空間。
- 如果字串長度超過 1MB時,每次擴容時只會擴充套件 1MB 的空間。
這樣既保證了記憶體空間夠用,還不至於造成記憶體的浪費,字串最大長度為 512MB
.。
以上圖片源自網路,如有侵權聯絡刪除
上圖就是字串的基本結構,其中 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
這個欄位,用來快速定位到最後一
個元素,然後倒著遍歷
以上圖片源自網路,如有侵權聯絡刪除
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、應用場景:
- 好友、關注、粉絲、感興趣的人集合:
1)sinter
命令可以獲得A和B兩個使用者的共同好友;
2)sismember
命令可以判斷A是否是B的好友;
3)scard
命令可以獲取好友數量;
4) 關注時,smove
命令可以將B從A的粉絲集合轉移到A的好友集合 - 首頁展示隨機:美團首頁有很多推薦商家,但是並不能全部展示,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五種基礎資料結構和應用場景,旨在給小夥伴們一個面試備題的方向,後續會持續輸出Redis方面的文章,歡迎關注,我們們一起學習拿offer。