Redis的String物件

陳俊成發表於2016-10-26

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

在Redis裡面,C字串只會作為字串字面量用在無需對字串值進行修改的地方,比如列印日誌。

SDS的定義

struct sdshdr{
    int len;//記錄buf陣列中使用位元組的數量,但不包括空字元
    int free;//記錄buf陣列中未使用位元組的數量;
    char buf[];//字元陣列
}

如圖:
這裡寫圖片描述

SDS與C字串的區別(重點)

  1. 常數複雜度獲取字串長度
    C字串本身不記錄自身的長度資訊,當需要獲取本身的長度時就不需迴圈,對遇到的字元進行記數,直到遇到代表字串結尾的空字元為止,這個操作的時間複雜度為O(N);而SDS本身記錄的自身的長度len,故可以以O(1)的時間複雜度獲取字串長度
  2. 杜絕緩衝區溢位
    C字串不記錄自身的長度,所以strcat假定使用者在執行這個函式時,已經為dest(目標字串)分配了足夠多的記憶體,可以容納src(源字串)字串的所有內容,而一旦這個假定不成立時,就會產生緩衝區溢位。
    這裡寫圖片描述

與C字串不同,SDS的空間分配策略杜絕了發生緩衝區溢位的可能性;當SDSAPI需要對SDS進行修改時,API會先檢查SDS的空間是否滿足修改所需的要求;如果不滿足的話,API會自動將SDS的空間擴充套件至執行修改所需的大小(當出現上圖情況,SDS會怎麼處理?),然後才執行實際的修改操作,所以使用SDS既不需要手動修改SDS的空間大小(C語言中需要自己分配空間,修改字元陣列的空間大小),也不會出現前面所說的緩衝區溢位問題。

  1. 減少修改字串時帶來的記憶體重分配次數
    兩個策略:(1)空間預分配;(2)惰性空間釋放
    為什麼SDS會減少修改字串時帶來的記憶體重分配次數,其實是跟它的分配策略是相關的。
    當進行拼接操作時,SDS會擴充空間,擴充多少空間呢?會擴充至拼接後len*2+1(1是空字元所佔用的位元組),比如原來有8個字元,1個空字元(即,len為8),但free是0,此時需要拼接“hello”,明顯空間不足,SDS將會分配空間,最終len等於13,free等於13,總共的空間為len+free+1。SDS不像C一樣,每次擴充至剛剛好夠用的空間,它會預留一些空間讓下次可能會進行的再拼接使用,所以因為這個策略,SDS就減少了修改字串帶來的記憶體重分配次數。即,C語言修改字元N次,它必須進行記憶體重分配N次;SDS修改字元N次,它最多進行記憶體重分配N次。(這種策略叫“空間預分配”)
    以上所講都是拼接strcat的情況,那如果進行釋放trim的情況呢?
    當SDS的API需要縮短SDS儲存的字串時,程式並不立即使用記憶體重分配來回收縮短後多出來的位元組,而是使用free屬性將這些位元組的數量記錄起來,並等待將來使用。(這種策略叫“惰性空間釋放”)
    這裡寫圖片描述

  2. 二進位制安全
    C字串裡面不能包含空字元,否則最先被吃呢工序讀入的空字元將誤認為是字串結尾,這些限制使得C字串只能儲存文字資料,而不能儲存像圖片、音訊、視訊、壓縮檔案這樣的二進位制資料。
    SDS的API都是二進位制安全的,所有SDS API都會以二進位制的方式來處理SDS存放在buf陣列李的資料,程式不會對其中的資料做任何限制、過濾或者假設,資料在寫入時是什麼樣的,它讀取時就是什麼樣。
    這也是我們將SDS的buf屬性稱為位元組陣列的原因——Redis不是用這個陣列來儲存字元,而是用它來儲存一系列二進位制資料。

    這裡寫圖片描述

相關文章