文章目錄
SDS(簡單動態字符串)
SDS的定義
SDS與C字符串的區別:其實就是改善,根據自身需求完善數據結構
常數複雜度獲取字符串長度
- 因爲C字符串並不記錄自身的長度信息,所以爲了獲取一個C字符擦混的長度,程序必須遍歷整個字符串,對遇到的每個字符串進行計數,直到遇到代表字符串結尾的空字符爲止,這個操作的複雜度爲O(N)
- 和C字符串不同,因爲SDS在len屬性中記錄了SDS本身的長度,所以獲取一個SDS長度的複雜度僅爲O(1)
對於執行Strlen()函數有較大的優勢
杜絕緩衝區溢出(類似StringBuilder)
與C字符串不同,SDS的空間分配策略完全杜絕了發生緩衝區溢出的可能性:當SDS API需要對SDS進行修改時,API會先檢查SDS的空間是否滿足修改所需的要求,如果不能滿足的話,API會自動將SDS的空間擴展至鎖需修改的大小,也不會出先
緩衝區溢出的問題
減少修改字符串時帶來的內存重分配次數(分配合適的內存大小)
- 若執行增長字符串的操作,比如拼接操作,在執行這個操作之前,需要通過內存重分配來擴展底層數組的大小空間
- 若執行縮短字符擦混的操作,比如截斷操作,在執行操作之前,需要通過內存重分配來釋放字符擦混不再使用的那部分空間(
防止出現內存泄露
)
爲了避免C字符串頻繁增長字符擦混的操作,SDS通過未使用空間解除了字符串長度和底層數組長度之間的關聯:在SDS中,buf數組的長度不一定就是字符數量+1,數組裏麪包含了未使用的字節,而這些字節的數量就是由SDS的free屬性記錄
空間預分配(減少重分配次數)
空間預分配用於優化SDS的字符擦混中增長操作:當SDS的API對一個SDS進行修改時,並且需要對SDS進行空間擴展的時候,程序不僅會爲SDS分配修改所需的空間,還會爲SDS分配額外的使用空間
- 若SDS的長度小於1MB,那麼程序將分配和
len
屬性同樣大小的未使用空間free
- 若SDS的長度大於等於1MB,那麼程序將會分配1MB的未使用空間
free
通過這種預分配策略,SDS將連續增長N次字符擦所需要的內存重分配次數從必定N次降低爲最多N次
惰性刪除
- 當刪除時,並不會真正刪除,只是將內存分給free以待將來使用
- 當然也有函數來真正釋放,避免內存浪費
二進制安全
通過使用二進制安全的SDS,而不是C字符串,使得Redis
不僅可以保存文本數據,還可以保存任意格式的二進制數據
兼容部分C字符串
鏈表
鏈表和鏈表節點的實現
Redis的鏈表實現的特性如下
雙端
:鏈表節點帶有pre
和next
指針,獲取某個節點的前置節點和後置節點的複雜度都是O(1)
無環
:鏈表頭節點的prev
和表尾節點的next
指針都指向null
,對鏈表的訪問以null
爲重點帶頭指針和表尾指針
帶鏈表長度技術器
:程序使用list
結構的len
屬性來對list
持有的鏈表節點進行計數,程序獲取鏈表中節點數量的複雜度爲O(1)
多態
:可以用來保存各種不同類型的值
字典
底層數據結構
- ht屬性是一個包含兩個項的數組,數組中的每個項都是一個
dictht
哈希表,一般情況下,字典只使用ht[0]
哈希表,ht[1]
哈希表只會在對ht[0]
哈希表進行rehash
時使用 - 除了
ht[1]
之外,另一個和rehash
有關的屬性就是rehashidx
,它記錄了rehash
目前的進度,如果目前沒有在進行進行rehash
,那麼它的值爲-1
存儲key的問題
哈希算法
當要將一個新的鍵值對添加到字典裏面時,程序需要先根據鍵值對堆鍵的計算出哈希值和索引值,然後再根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定的索引上面
//使用字典設置的哈希函數,計算鍵key的哈希值
hash=dict->type->hashFunction(key);
//使用哈希表的sizemask屬性和哈希值,計算出索引
index = hash&dict->ht[x].sizemask;
當字典被用作數據庫的底層實現,或者哈希鍵 的底層實現時,Redis使用MurmurHash2
算法來計算鍵的哈希值
解決鍵重提
當有兩個或以上數量的鍵被分配到了哈希數組的同一個索引時,我們稱爲鍵發生了衝突
使用鏈地址發
rehash
何時rehash
如何rehash
漸進式rehash
若ht[0]中保存的鍵值對個數比較多時,那麼要一次性將這些鍵值對全部rehash到ht[1]的話,龐大的計算量可能會導致服務器在一段時間內停止服務
爲了避免rehash對服務器性能造成影響,服務器不是一次性將ht[0]裏面的所有的鍵值對全部rehash到ht[1],而是分多次,漸進式的將ht[0]裏面的鍵值對慢慢的rehash到ht[1]
小結
跳躍表
跳躍表是一種有序數據結構,它通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的
Redis使用跳躍表作爲有序集合鍵的底層實現之一,如果一個有序集合包含的元素數量比較多,又或者有序集合中的元素的成員是比較長的字符串時,Redis就會使用跳躍表來作爲有序集合鍵的底層實現
Redis中應用
Redis只在兩個地方用到了跳躍表,一個是實現有序集合鍵,另一個是在集羣節點中用作內部數據結構
跳躍表的實現
- 層(Level):節點中用
L1
,L2
,L3
等字樣標記節點的各個層,L1帶表第一層,L2代表第二層,一次類推。每個層都帶有兩個屬性:前進指針和跨度。前進指針用於訪問表尾方向的其他節點,而跨度則記錄了前進指針指向節點和當前節點的距離。 後退(backward)指針
:節點中用BW
字樣標記節點的後退指針,它指向位於當前節點的前一個節點,後退指針再程序從表尾向表頭節點遍歷時使用。分值
:各個節點的1.0、2,.0、和3.0是節點所保存的分值。在跳躍表中,節點按各自所保存的分支從小到大排列。成員對象
:各個節點的o1
,o2
和o3
是節點所保存的成員對象,
在同一個跳躍表中,各個節點保存的成員對象必須是唯一的,但是多個節點保存的分值卻可以是相同的;分值相同的節點按照成員對象在字典序中的大小來進行排序,成員對象較小的節點會排在前面(靠近表頭的方向),而成員對象較大的節點則會排在後面
跳躍表和AVL
小結
整數集合
作用
整數集合是集合鍵的底層實現之一,當一個集合鍵只包含整數值元素,並且這個集合的元素數量不多時,Redis就會使用整數集合作爲集合鍵的底層實現
實現
整數集合是Redis用於保存整數值的集合抽象數據結構,它可以保存類型
int16_t
、int32_t
或者int64_t
的整數值,並且保證集合中不會出現重複元素。
元素類型升級
當我們要將一個新元素添加到整數集合裏面,並且新元素的類型要比整數集合現有所有元素的類型都要長時,整數集合需要先進行升級,然後才能將新元素添加到整數集合裏面
升級的好處
整數集合升級的策略有兩個好處:一個是提升整數集合的靈活性;另一個是儘可能的節約內存。
降級
整數集合不支持降級操作,一旦對數組進行了升級,編碼就會一直保持升級後的狀態。
壓縮列表
作用
壓縮列表是列表鍵
和哈希鍵
的底層實現之一。當一個列表鍵只包含少量列表項,並且每個列表項要麼就是小整數值。要麼就是長度比較短的字符串,那麼Redis就會使用壓縮列表來做列表鍵的底層實現
哈希鍵裏面包含的所有鍵和值都是小整數值或者段字符串
實現
壓縮列表是
Redis
爲了節約內存而開發的,是一系列特殊編碼的連續內存塊組成的順序性數據結構。一個壓縮列表可以包含任意多個節點,每個節點可以保存一個字節數組或者一個整數值。
壓縮列表節點的構成-entry的構成
- content:負責保存節點的值,節點值可以是一個字節數組或者整數,值的類型和長度由節點的
encoding
屬性決定