1. 程式人生 > >Redis原始碼剖析(二)--簡單動態字串

Redis原始碼剖析(二)--簡單動態字串

 Redis沒有使用C語言的字串結構,而是自己設計了一個簡單的動態字串結構sds。它的特點是:可動態擴充套件記憶體、二進位制安全和與傳統的C語言字串型別相容。下面就從原始碼的角度來分析一下Redis中sds的實現。

 

1 SDS的定義

// sds相容傳統C風格字串,所以起了個別名叫sds,並且可以存放sdshdr結構buf成員的地址
typedef char *sds; 

SDS也有一個表頭(header)用來存放sds的資訊。

/*
 * 儲存字串物件的結構
 */
struct sdshdr {
    
    // buf 中已佔用空間的長度
    int
len; // buf 中剩餘可用空間的長度 int free; // 資料空間 char buf[]; };

以下為一個SDS示例:

len為5,表示這個sds長度為5位元組。
free為0,表示這個sds沒有未使用的空間。
buf是一個char[]的陣列,分配了(len+1+free)個位元組的長度,前len個位元組儲存著’R’、’e’、’d’、’i’、’s’這5個字元,接下來的1個位元組儲存著’\0’,剩下的free個位元組未使用。

 

2 SDS的優點

 

2.1 杜絕緩衝區溢位

因為SDS表頭的free成員記錄著buf字元陣列中未使用空間的位元組數,所以,在進行APPEND命令向字串後追加字串時,如果不夠用會先進行記憶體擴充套件,在進行追加。

總之,正是因為表頭的存在,使得redis的字串有這麼多優點。

2.2 獲得字串長度的操作複雜度為O(1)

傳統的C字串獲得長度時的做法:遍歷字串的長度,遇零則止,複雜度為O(n)。

而SDS表頭的len成員就儲存著字串長度,所以獲得字串長度的操作複雜度為O(1)。

2.3 二進位制安全(Binary Safe)

因為傳統C字串符合ASCII編碼,這種編碼的操作的特點就是:遇零則止 。即,當讀一個字串時,只要遇到’\0’結尾,就認為到達末尾,就忽略’\0’結尾以後的所有字元。因此,如果傳統字串儲存圖片,視訊等二進位制檔案,操作檔案時就被截斷了。

 

3 SDS記憶體分配及釋放策略

 

3.1 SDS記憶體分配策略—空間預分配

空間預分配策略用於優化SDS的字串增長操作。

如果對SDS進行修改後,SDS表頭的len成員小於1MB,那麼就會分配和len長度相同的未使用空間。free和len成員大小相等。
如果對SDS進行修改後,SDS的長度大於等於1MB,那麼就會分配1MB的未使用空間。
通過空間預分配策略,Redis可以減少連續執行字串增長操作所需的記憶體重分配次數。

原始碼如下:

sds sdsMakeRoomFor(sds s, size_t addlen) 
{
    struct sdshdr *sh, *newsh;

    // 獲取 s 目前的空餘空間長度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空餘空間已經足夠,無須再進行擴充套件,直接返回
    if (free >= addlen) return s;

    // 獲取 s 目前已佔用空間的長度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的長度
    newlen = (len+addlen);

    // 根據新長度,為 s 分配新空間所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新長度小於 SDS_MAX_PREALLOC 
        // 那麼為它分配兩倍於所需長度的空間
        newlen *= 2;
    else
        // 否則,分配長度為目前長度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

    if (newsh == NULL) return NULL;

    // 更新 sds 的空餘長度
    newsh->free = newlen - len;

    return newsh->buf;
}

 

3.2 SDS記憶體釋放策略—惰性空間釋放

惰性空間釋放用於優化SDS的字串縮短操作。

當要縮短SDS儲存的字串時,程式並不立即使用記憶體充分配來回收縮短後多出來的位元組,而是使用表頭的free成員將這些位元組記錄起來,並等待將來使用。
原始碼如下:

 

void sdsclear(sds s) 
{
    // 取出 sdshdr
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    // 重新計算屬性
    sh->free += sh->len;
    sh->len = 0;

    // 將結束符放到最前面(相當於惰性地刪除 buf 中的內容)
    sh->buf[0] = '\0';
}