Redis:數據結構與對象 (簡單對象字符串SDS)

Redis 數據庫裏面的每個鍵值對(key-value pair) 都是由對象(object)組成,其中

  • 數據庫鍵總是一個字符串對象(string object);
  • 數據庫值則可以是字符串對象(string object)、列表對象(list object) 、哈希對象(hash object)、集合對象(set object)、有序集合對象(sorted set object) 這五種對象中的一種。

Redis 是用C實現的,但是Redis中沒有直接使用C語言傳統的字符串表示,而是構建了一種名爲簡單動態字符串的抽象類型(simple dynamic string)SDS,作爲Redis的默認字符串表示。

/*
 *  /src/sds.h
 * 保存字符串對象的結構
 */
struct sdshdr {
    
    // buf 中已佔用空間的長度
    //記錄buf數組中已使用的字節的數量,等於SDS所保存的字符串的長度
    int len;

    // buf 中剩餘可用空間的長度
    int free;

    // 數據空間
    char buf[];
};

在這裏插入圖片描述
SDS遵循C字符串以空字符結尾的慣例,保存空字符的一字節空間不計算在SDS的len屬性裏面,並且爲空字符分配額外的一字節空間。添加空字符到字符串末位等操作,是有SDS函數自動完成,對於使用者來說是完全透明的。這樣的好處是,SDS可以直接重用一部分C字符串函數庫裏面的函數。比如

printf("%s",s->buf);

來打印SDS保存的字符串值,無需爲SDS編寫專門的打印函數。

對C字符串和SDS之間的區別,和原因:
在這裏插入圖片描述

  1. 因爲C字符串並不記錄自身的長度信息,所以爲了獲取一個C字符串的長度,程序必須遍歷整個字符串。和C字符串不同,因爲SDS在len屬性中記錄了SDS本身的長度,所以可以直接獲取得到。
  2. C中字符串的拼接,假設程序裏有兩個在內存中緊鄰的C字符串s1和s2,如下:
    在這裏插入圖片描述如果一個在執行strcat(s1," Cluster")之前,沒有給s1分配足夠的空間。那麼在執行strcat函數之後,s1的數據將溢出到s2所在的空間中,導致s2保存的內容被意外的修改。與C字符串不同,當SDS修改時,會先檢查SDS的空間是否滿足修改所需的要求,不滿足的話會自動將空間擴展,這樣就不會出現緩衝區溢出的問題。
  3. 因爲C字符串並不記錄自身的長度,所以對於一個包含了N個字符串的C字符串來說,這個C字符串的底層實現總是一個N+1的字符長的數組。所以每次增加或者縮短一個C字符串,程序總要對保存這個C字符串的數組進行一次內存的重新分配操作。比如拼接操作(append),執行前需要先通過內存的重新分配來擴展底層數組的空間大小,否則這一步就會產生緩衝區溢出。比如截斷操作(trim),執行之後需要通過內存重新分配釋放字符串不再使用的空間,否則這一步會產生內存的泄露。爲了避免C字符串的這種缺陷,SDS解除了字符串長度和底層數組長度之間的關聯:在SDS中,buf數組的長度不一定就是字符數量加一,數組裏面可以包含未使用的字節,而這些字節的數量就由free屬性記錄。通過未使用空間,SDS實現了空間預分配和惰性空間釋放優化策略。
  • 空間預分配:
    對SDS進行增加操作,需要空間擴展時(free的大小小於需要添加的長度),如果擴展後SDS的長度(len)小於1MB,那麼程序將分配len大小的未使用空間,這時SDS len屬性的值將和free屬性的相同。實際buf長度爲 len + len+1 。對於增加後 len大於1MB,程序會分配1M的未使用空間,實際的buf長度爲 len + 1MB+ 1byte。
    在這裏插入圖片描述
    sdscat(s," cluster");

在這裏插入圖片描述

  • 惰性釋放
    當SDS需要縮短SDS保存的字符串時,程序並不立即使用內存重分配來回收縮短後多出來的字節空間,而是使用free記錄起來,等待將來使用。
    在這裏插入圖片描述sdstrim(s,"XY"); //移除SDS字符串中所有X和Y
    在這裏插入圖片描述於此同時,SDS也提供了相應的API,可以在有需要的時候,真正的釋放出SDS未使用空間sdsfree()。
  1. 對於保存數據的格式,C字符串中的字符必須符合某種編碼,並且除了字符串的末尾之外,字符串裏面不能包含空字符,否則最先被程序讀入的空字符將被誤認爲是字符串的結尾,所以限制了C字符串只能保存文本數據,不能夠保存像圖片、音視頻、壓縮文件這樣的二進制數據。而SDS是使用len的值來判斷字符串是否結束,所以沒有任何問題。
  2. 因爲SDS一樣遵循C字符串以空字符結尾的慣例,是爲了讓保存文本數據的SDS可以重用一部分<string.h> 庫定義的函數,減少不必要的代碼重複。

總結:

  • Redis只會使用C字符串作爲字面量,在大多數情況下,Redis使用SDS作爲字符串表示。
  • 比起C字符串,SDS有一下優點:
    • 能夠直接獲取字符串長度
    • 杜絕緩衝區溢出
    • 減少修改字符串長度時所需的內存重分配次數
    • 二進制安全
    • 兼容部分C字符串函數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章