1. 程式人生 > >Redis基礎知識----SDS(簡單動態字串)

Redis基礎知識----SDS(簡單動態字串)

以下的總結,基於redis原始碼4.0.9版本

1、redis的底層儲存資料結構
簡單動態字串(SDS)、連結串列、字典、跳躍表、整樹集合、壓縮列表等
2、redis的資料結構物件
字串、列表、雜湊、集合、有序集合等

  1. 簡單動態字串(sds)

    SDS通過字串的長短不同實現了5中不同的資料結構,其不同在於頭部用來標識字串長度的型別,主要用來減小結構體的大小,節省記憶體。
    例如:長度小於32的字串,則會使用sdshdr5結構體,sdshdr5結構體特殊在並沒有lenalloc屬性,其巧妙的使用了flags的高5位用來標識字串的長度。

 sdshdr5
----> <32 sdshdr8 ----> <256 sdshdr16 ----> < 2^16 sdshdr32 ----> < 2^32 sdshdr64 ----> < 2^64

基本資料結構如下:

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; };

len 代表字串的真實長度

alloc 排除header和terminator外的分配長度

flags 型別標識了結構體的實際型別

buf[] 真實資料

sdshdr5資料結構

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char
buf[]; };

字串的擴容

/*
 * 在必要情況下對SDS進行擴容
 *
 * 引數列表
 *      1. s: 待擴容對SDS對字串指標
 *      2. addlen: 需要新加入字串的長度
 *
 * 返回值
 *      返回擴容後新的sds,如果沒擴容則和入參sds地址相同
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    // 首先計算出原SDS還剩多少可分配空間
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

    /* Return ASAP if there is enough space left. */
    // 已經夠用的情況下直接返回
    if (avail >= addlen) return s;

    len = sdslen(s);
    // 用sds(指向結構體尾部,字串首部)減去結構體長度得到結構體首部指標
    // 結構體型別是不確定的,所以是void *sh
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    // 如果新長度小於最大預分配長度則分配擴容為2倍
    // 如果新長度大於最大預分配長度則僅追加SDS_MAX_PREALLOC長度
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    // 字串的長度更改了,使用對頭部型別可能也會變化
    type = sdsReqType(newlen);
    // 由於SDS_TYPE_5沒有記錄剩餘空間(用多少分配多少),所以是不合適用來進行追加的
    // 為了防止下次追加出現這種情況,所以直接分配SDS_TYPE_8型別
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {
        // 型別沒變化則直接使用原起始地址重新分配下記憶體即可
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        // 頭部型別有變化則重新開闢一塊記憶體並將原先整個SDS拷貝一份過去
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        // 舊的已經沒用了
        s_free(sh);
        s = (char*)newsh+hdrlen;
        // 配置新型別
        s[-1] = type;
        sdssetlen(s, len);
    }
    // 設定新對分配對總長度
    sdssetalloc(s, newlen);
    return s;
}

關鍵知識點:
1.SDS記錄長度,O(1)獲取整個長度
2.減少字串修改時記憶體分配次數。記憶體預分配,分配一定長度的字串,擴容時如果擴容需要的len小於SDS_MAX_PREALLOC,則擴大len的2倍,否則擴大SDS_MAX_PREALLOC(1M)(#define SDS_MAX_PREALLOC (1024*1024))。通過len和alloc實現惰性釋放,字串刪除後,長度不變,便於下次使用。
3.二進位制安全。不是靠空字元來判斷字串的結束的,而是通過len這個屬性。
4.杜絕快取去溢位。字串合併前,會檢查要當前字串的長度,不足,擴容。規則如 2.