Redis3.20閱讀-SDS實現

聲明:這是本人蔘考黃建宏的《redis設計與實現》(源碼版本是redis3.0)來學習redis3.20源碼的筆記,若是有什麼不對的地方,歡迎你們指正,你們一塊兒學習、一塊兒進步,QQ:499656254。redis

1、SDS介紹

    SDS又叫簡單動態字符串,在Redis中默認使用SDS來表示字符串。好比在Redis中的鍵值對中的鍵通常都是使用SDS來實現。首先須要說明的是在Redis中,字符串不是用傳統的字符串來實現,而是Redis本身構建了一個結構來表示字符串。優勢以下:數組

一、O(1)時間內獲取字符串長度。(依據其結構特性,只須要訪問其結構體成員len既可得到字符串長度)安全

二、SDS提供的一些API操做,是二進制安全的(也就是不會由於空格等特殊字符而中斷字符串)、不會溢出(API操做會檢查其長度)數據結構

三、減小了修改字符串時帶來的內存重分配次數。app

      對於增加字符串其採用的策略是檢查修改以後的長度大小,若是小於1024*1024,則分配2倍的修改後的長度+1函數

     對於減小的字符串其並不當即釋放空間,而是迴歸到alloc中去。學習

這個構建的結構在Redis3.20中的表示以下(和Redis2.x中仍是有必定區別的):優化

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
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[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

 從代碼中能夠看出,SDS表示的字符串是有SDSheader和char*指針組成,而SDS的頭部主要由四部分組成:ui

                  len:SDS字符串已使用的空間。this

                  alloc:申請的空間,減去len就是未使用的空間,初始時和len一致。

                  flag:只使用了低三位表示類型,細化了SDS的分類,根據字符串的長度的不一樣選擇不一樣的sds結構體,而結構體的主要區別是len和alloc的類型,這樣作能夠節省一                           部分空間大小,畢竟在redis字符串很是多,進一步的能夠節省空間。

                  buf:  用了C的特性表示不定長字符串。

2、API學習

    一、sdsnewlen函數

          函數原型:sds sdsnewlen(const void *init, size_t initlen)

          說明:sdsnewlen用來建立init所指向對象做爲內容的SDS,好比mystring = sdsnewlen("abc",3)。其中sdsnew函數也是調用sdsnewlen函數來實現的

          返回值:buf數組的指針位置

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);//根據initlen的長度,選擇不一樣的type,進一步來節省內存空間                                                                                                                                                        
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);//返回sdshdr結構體大小
    unsigned char *fp; /* flags pointer. */

    sh = s_malloc(hdrlen+initlen+1);//底層調用malloc申請空間
    if (!init)
        memset(sh, 0, hdrlen+initlen+1);//若建立的sds對象爲空,則空間賦值0
    if (sh == NULL) return NULL;//分配失敗返回NULL
    s = (char*)sh+hdrlen;//指向buf數組
    fp = ((unsigned char*)s)-1;//指向flag
    switch(type) {//根據type不一樣對sdshdr結構體進行賦值,len和alloc設置爲initlen
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    if (initlen && init)//將字符串拷貝至分配的內存空間
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}

    二、sdsMakeRoomFor函數

        函數原型:sds sdsMakeRoomFor(sds s, size_t addlen)

       說明:實現擴充已有sds的可用空間爲指定的大小,擴充規則是:當addlen的長度小於1024*1024時,則申請的空間是2*(addlen+len),不然擴充爲1024*1024大小。

       返回值:擴充後的sds對象

sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);//返回剩餘可用空間,即s->alloc - s->len
    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);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {    //若類型和原有類型同樣,則採用realloc分配空間,不然從新分配採用malloc函數分配。
        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 */
        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;
}

  三、sdstrim函數

         函數原型:sds sdstrim(sds s, const char *cset)

         說明:從左右兩邊剔除sds對象包含集合CSET中的元素,內部經過memmove函數移位實現。

sds sdstrim(sds s, const char *cset) {
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;
    ep = end = s+sdslen(s)-1;
    while(sp <= end && strchr(cset, *sp)) sp++;
    while(ep > sp && strchr(cset, *ep)) ep--;
    len = (sp > ep) ? 0 : ((ep-sp)+1);
    if (s != sp) memmove(s, sp, len);
    s[len] = '\0';
    sdssetlen(s,len);
    return s;
}

3、結論

    sds數據類型是redis裏面經常使用的數據類型,因此其在設計優化上面有了必定的改動(相對於redis2.x版本),好比其數據結構發生了改變。最後想說下,其源碼實現確實比較簡單,可是代碼寫的很nice(至少目前的我還寫不出來,不過我要加油)

相關文章
相關標籤/搜索