1. 程式人生 > 實用技巧 >帶你深入瞭解Redis字串資料結構

帶你深入瞭解Redis字串資料結構

    

1 前言

  Redis資料庫裡面的每個鍵值對都是物件組成的,其中:
  • 資料庫鍵總是一個字串物件(string object);
  • 而資料庫鍵的值則可以是字串物件(string)、列表物件(list)、雜湊物件(hash)、集合物件(set)、有序集合物件這五種物件中的一種。
  字串物件作為Redis五種資料結構中最常用的一種,我們有必要了解該物件的底層資料結構,底層資料結構又是如何深刻地影響物件的功能和效能的,以便我們更好地實現使用它或者出現錯誤的時候更好地排查。

2 正文

  Redis沒有直接使用C語言傳統的字串表示,而是自己構建了一種名為簡單動態字串(simple dynamic string, SDS)的抽象型別,並將SDS用作Redis的預設字串表示。

 2.1 SDS的底層資料結構

struct sdshdr{
    //記錄buf陣列中已使用位元組的數量
    //等於SDS所儲存字串的長度
    int len;
    
    //記錄buf陣列中未使用位元組的數量
    int free;
    
    //位元組陣列,用於儲存字串
    char buf[];
};
  SDS示例

 2.2 SDS物件與C語言字串物件的異同點:

   相同點:

     1、以空字元 '\0' 結尾,併為空字元分配額外的空間(不算在字元長度內)

   不同點:

     1、C語言字串物件底層的資料結構是字元陣列,SDS物件底層資料結構是位元組陣列      2、SDS設定len儲存字串長度,C語言字串通過遍歷陣列獲得字串長度      3、SDS設定free記錄buf陣列未使用位元組的長度

 2.3 SDS物件的優點

  1、常數複雜度獲取字串長度

  C字串並不記錄自身的長度資訊,獲取一個C字串的長度,程式必須遍歷整個字串,對遇到的每個字元進行技術,直到遇到代表字串結尾的空字元為止,這個操作的複雜度為O(N)。 SDS在len屬性中記錄了SDS本身的長度,獲取一個SDS長度的複雜度僅為O(1)。

  2、杜絕緩衝區溢位

  C字串不記錄自身的長度帶來的另一個問題是容易造成緩衝區溢位。   而當SDS API需要對SDS進行修改時,API會先檢查SDS空間是否滿足修改所需的要求,如果不滿足的話,API會自動將空間擴充套件至執行所需的大小,然後才執行實際的修改操作。

  3、減少修改字串時帶來的記憶體重分配次數

  C字串每次增長或者縮短一個C字串時,程式都要對儲存這個C字串的陣列進行一次記憶體重分配操作。   SDS實現了空間預分配和惰性空間釋放兩種記憶體分配策略,避免每次修改字串都執行一次記憶體重分配。
  • 空間預分配:空間預分配用於優化SDS的字串增長操作。當SDS需要空間擴充套件時,程式不僅會為SDS分配修改所必須要的空間,還會為SDS分配額外的未使用空間。未使用空間數量由如下公示決定:
    • 如果對SDS進行修改之後,SDS的長度(len的值)小於1mb,那麼就預分配和原陣列長度大小的未使用空間(即free = len)。
    • 如果對SDS進行修改之後,SDS的長度(len的值)大於1mb,那麼就預分配1mb的未使用空間。舉個例子,如果進行修改之後,SDS的len將變成30mb,那麼程式分配1mb的未使用空間,buf陣列的實際長度將為30mb + 1mb + 1byte。
    • 通過空間預分配策略,Redis可以減少連續執行字串增長操作所需的記憶體分配次數。
  • 惰性空間釋放:當執行SDS字元陣列的縮減時,程式並不立即回收縮短後多出來的位元組,而是使用free屬性將這些位元組的數量記錄起來,並等待將來使用。通過惰性空間釋放策略,SDS避免了縮短字串所需的記憶體重分配操作,併為將來可能有的增長操作提供了優化。與此同時,SDS也提供了相應的API,在有需要時真正去釋放SDS的未使用空間,所以不用擔心該策略造成記憶體浪費。

  4、二進位制安全

  不同於C字串的只能儲存符合某種編碼(比如ASCII)字元的字元陣列,Redis所以SDS API會以二進位制的方式來處理SDS存放在buf數組裡的資料,程式不會對陣列做任何的限制、過濾,陣列寫入時是什麼樣的,它被讀取是就是什麼樣,這也是為什麼將SDS的buf屬性稱為位元組陣列的原因。Redis不僅可以儲存文字資料,還可以儲存任意格式的二進位制資料。

  5、相容部分C字串函式

  SDS遵循C字串以空字元結尾的慣例,是為了讓那些儲存文字資料的SDS可以重用一部分<string.h>庫定義的函式。   舉個例子,如果我們有一個儲存“Redis”文字資料的SDS值sds,那麼我們就可以重用<string.h>/strcasecmp函式,使用它來對比另一個C字串:     strcasecmp(sds->buf, "hello world");    這樣Redis就不用自己專門去寫一個函式來對比SDS值和C字串值了。

3 總結

  • Redis只會使用C字串作為字面量,在大多數情況下,Redis使用SDS作為字串表示。
  • 比起C字串,SDS具有以下優點:
    • 常數複雜度獲取字串長度
    • 杜絕緩衝區溢位
    • 減少修改字串時帶來的記憶體重分配次數
    • 二進位制安全
    • 相容部分C字串函式