Redis數據類型與底層實現


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的鏈表實現的特性如下

  • 雙端:鏈表節點帶有prenext指針,獲取某個節點的前置節點和後置節點的複雜度都是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是節點所保存的分值。在跳躍表中,節點按各自所保存的分支從小到大排列。
  • 成員對象:各個節點的o1o2o3是節點所保存的成員對象,
    在同一個跳躍表中,各個節點保存的成員對象必須是唯一的,但是多個節點保存的分值卻可以是相同的;分值相同的節點按照成員對象在字典序中的大小來進行排序,成員對象較小的節點會排在前面(靠近表頭的方向),而成員對象較大的節點則會排在後面

跳躍表和AVL

在這裏插入圖片描述

小結

在這裏插入圖片描述

整數集合

作用

整數集合是集合鍵的底層實現之一,當一個集合鍵只包含整數值元素,並且這個集合的元素數量不多時,Redis就會使用整數集合作爲集合鍵的底層實現

實現

整數集合是Redis用於保存整數值的集合抽象數據結構,它可以保存類型int16_tint32_t或者int64_t的整數值,並且保證集合中不會出現重複元素。

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

元素類型升級

當我們要將一個新元素添加到整數集合裏面,並且新元素的類型要比整數集合現有所有元素的類型都要長時,整數集合需要先進行升級,然後才能將新元素添加到整數集合裏面

在這裏插入圖片描述

升級的好處

整數集合升級的策略有兩個好處:一個是提升整數集合的靈活性;另一個是儘可能的節約內存。

降級

整數集合不支持降級操作,一旦對數組進行了升級,編碼就會一直保持升級後的狀態。

在這裏插入圖片描述

壓縮列表

作用

壓縮列表是列表鍵哈希鍵的底層實現之一。當一個列表鍵只包含少量列表項,並且每個列表項要麼就是小整數值。要麼就是長度比較短的字符串,那麼Redis就會使用壓縮列表來做列表鍵的底層實現
哈希鍵裏面包含的所有鍵和值都是小整數值或者段字符串

實現

壓縮列表是Redis爲了節約內存而開發的,是一系列特殊編碼的連續內存塊組成的順序性數據結構。一個壓縮列表可以包含任意多個節點,每個節點可以保存一個字節數組或者一個整數值。

在這裏插入圖片描述

壓縮列表節點的構成-entry的構成

在這裏插入圖片描述

  • content:負責保存節點的值,節點值可以是一個字節數組或者整數,值的類型和長度由節點的encoding屬性決定
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章