Redis源碼閱讀筆記-動態字符串(SDS)結構

Redis中採用自定義的結構來保存字符串,在sds.h中:數組

/* 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由4部分組成:安全

  • len: SDS字符串已經使用的空間(不包含C中字符串的結束符的長度1)。
  • alloc: 申請的空間大小,減去len就是未使用的空間,初始時和len一直。
  • flags: 使用低三位表示類型,細分SDS的分類。方便根據字符串的長度不一樣選擇不用的SDS結構體,節省一部分空間。
  • buf: 用了C的不定長字符串。

PS: __attribute__ ((__packed__))關鍵字的解釋,在C/C++中,創建一個結構體時,會進行字節對齊操做,使得結構體的大小比其變量佔用的字節要多一些,當結構體上聲明中加上__attribute__ ((__packed__)),則表示取消字節對齊,按照緊湊排列的方式。app

// sds.h

typedef char *sds;

......
// 用法是 SDS_HDR(8, s),T傳入的是 sdshdr 的數字,s是buf字符串的地址
// SDS_HDR_VAR(T,s) 的做用是從 傳入的buf(參數s)的字符串地址 得到一個指向結構體的指針 sh
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
// SDS_HDR(T, s),的做用是經過T,和傳入的buf(參數s)的字符串地址,找到結構體的首地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

// 函數的做用是經過傳入的sds結構體的buf字符串地址,得到字符串的長度
static inline size_t sdslen(const sds s) {
    // 傳入的s是結構體 sdshdr 中 buf 的字符串地址,因此s[-1] 指向 flags
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

部分函數代碼解析

  • sds sdsnewlen(const void *init, size_t initlen) 建立一個包含給定C字符串的SDS:curl

    // sds.c
    
    	static inline char sdsReqType(size_t string_size) {
    	    if (string_size < 1<<5)
    	        // 字符串長度少於32 
    	        return SDS_TYPE_5;
    	    if (string_size < 1<<8)
    	        // 字符串長度少於256
    	        return SDS_TYPE_8;
    	    if (string_size < 1<<16)
    	        // 字符串長度少於65536
    	        return SDS_TYPE_16;
    	#if (LONG_MAX == LLONG_MAX)
    	    if (string_size < 1ll<<32)
    	        // 字符串長度少於2^32
    	        return SDS_TYPE_32;
    	#endif
    	    return SDS_TYPE_64;
    	}
    
    	/* Create a new sds string with the content specified by the 'init' pointer
    	 * and 'initlen'.
    	 * If NULL is used for 'init' the string is initialized with zero bytes.
    	 *
    	 * The string is always null-termined (all the sds strings are, always) so
    	 * even if you create an sds string with:
    	 *
    	 * mystring = sdsnewlen("abc",3);
    	 *
    	 * You can print the string with printf() as there is an implicit \0 at the
    	 * end of the string. However the string is binary safe and can contain
    	 * \0 characters in the middle, as the length is stored in the sds header. 	*/
    	sds sdsnewlen(const void *init, size_t initlen) {
    	    // *init 是字符串的首地址,initlen是字符串的長度(不包含字符串結束符)
    
    	    // 指向sds結構體的指針
    	    void *sh;
    	    sds s;
    
    	    // 根據字符串的長度,來指定sds的結構體
    	    // type 是表示sds的類型,具體數值查看sds.h
    	    char type = sdsReqType(initlen);
    
    	    // SDS_TYPE_5 再也不使用,最小單位是SDS_TYPE_8
    	    /* 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);
    	    unsigned char *fp; /* flags pointer. */
    
    	    // 申請結構體所需的內存,hdrlen是結構體的大小,initlen是字符串的長度,1是字符串結束符的長度
    	    sh = s_malloc(hdrlen+initlen+1);
    	    if (!init)
    	        memset(sh, 0, hdrlen+initlen+1);
    	    if (sh == NULL) return NULL;
    
    	    // s 指向告終構體中buf的首地址,爲下面將字符串內容複製到buf中作準備
    	    s = (char*)sh+hdrlen;
    	    // fp 指向告終構體中flags的地址
    	    fp = ((unsigned char*)s)-1;
    	    switch(type) {
    
    	        // 下面是給sds結構體的len, alloc, fp賦值
    
    	        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;
    	        }
    	    }
    
    	    // 將 init 的字符串拷貝到結構體的 buf 中
    	    if (initlen && init)
    	        memcpy(s, init, initlen);
    
    	    // 給buf的末尾加上結束符
    	    s[initlen] = '\0';
    	    return s;
    	}
  • void sdsfree(sds s)釋放給定的sds內存:函數

    // sds.c
    
    	/* Free an sds string. No operation is performed if 's' is NULL. */
    	void sdsfree(sds s) {
    		 // 傳入的s,是指向sds結構體中buf字符串的首地址的指針
    
    	    if (s == NULL) return;
    	    // s_free是zmalloc.c中的zfree()函數,釋放內存
    	    // s[-1] 是得到指向sds結構體中flags的地址
    	    // sdsHdrSize(s[-1]) 則是得到對應的sds結構體的size
    	    // 因此(char*)s-sdsHdrSize(s[-1])則是得到指向sds結構體的地址
    	    s_free((char*)s-sdsHdrSize(s[-1]));
    	}
  • sds sdscat(sds s, const char *t)爲sds字符串後追加字符串,能夠看出,若是這個函數會分配一個額外的內存空間來做爲預留使用:性能

    // sds.c
    
    	/* Append the specified null termianted C string to the sds string 's'.
    	 *
    	 * After the call, the passed sds string is no longer valid and all the
    	 * references must be substituted with the new pointer returned by the call. */
    	sds sdscat(sds s, const char *t) {
        //經過sdscatlen拼接sds字符串, 將C字符串t拼接到sds字符串s上
    	    return sdscatlen(s, t, strlen(t));
    	}
    
    	/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
    	 * end of the specified sds string 's'.
    	 *
    	 * After the call, the passed sds string is no longer valid and all the
    	 * references must be substituted with the new pointer returned by the call. */
    	sds sdscatlen(sds s, const void *t, size_t len) {
    
       // 經過sdslen()獲取sds字符串的長度curlen
    	    size_t curlen = sdslen(s);
    
    	    s = sdsMakeRoomFor(s,len);
    	    if (s == NULL) return NULL;
    	    // s爲sds結構體中buf的地址指針,
    	    // s+curlen爲當前sds中字符串的末尾地址
    	    // memcpy(s+curlen, t, len) 則是將字符串寫入buf後
    	    memcpy(s+curlen, t, len);
    	    // 將sds的長度(len)設置爲curlen+len
    	    sdssetlen(s, curlen+len);
    	    // 在字符串末尾添加結束字符
    	    s[curlen+len] = '\0';
    	    return s;
    	}
    
    	/* Enlarge the free space at the end of the sds string so that the caller
    	 * is sure that after calling this function can overwrite up to addlen
    	 * bytes after the end of the string, plus one more byte for nul term.
    	 *
    	 * Note: this does not change the *length* of the sds string as returned
    	 * by sdslen(), but only the free buffer space we have. */
    	// 給sds的字符空間增大addlen的大小
    	sds sdsMakeRoomFor(sds s, size_t addlen) {
    	    void *sh, *newsh;
    
    	    // sdsavail()是計算sds有多少空閒空間,因此avail是該sds的空閒空間
    	    size_t avail = sdsavail(s);
    	    size_t len, newlen;
    	    // s[-1]是指向該sds的flags,s[-1] & SDS_TYPE_MASK 則得到sds的結構體類型
    	    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    	    int hdrlen;
    
    	    // 若是avail空閒空間比所需空間大,則直接返回
    	    /* Return ASAP if there is enough space left. */
    	    if (avail >= addlen) return s;
    
    	    // 得到sds的長度len
    	    len = sdslen(s);
    	    // 得到指向sds結構體的指針sh
    	    sh = (char*)s-sdsHdrSize(oldtype);
    	    newlen = (len+addlen);
    
    	    // 若是新的長度少於SDS_MAX_PREALLOC(1024*1024),則申請多一倍的空間做爲預留
    	    if (newlen < SDS_MAX_PREALLOC)
    	        newlen *= 2;
    	    else
    	        // 若是新的長度大於等於SDS_MAX_PREALLOC(1024*1024),則申請多一個若是新的長度大於等於SDS_MAX_PREALLOC的空間做爲預留
    	        newlen += SDS_MAX_PREALLOC;
    
    	    // 根據新的空間長度,獲取sds的結構體類型
    	    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爲新sds結構體的大小
    	    hdrlen = sdsHdrSize(type);
    	    if (oldtype==type) {
    	        // 當sds結構體類型不須要改變時
    	        // 經過realloc()在本來結構體上擴大內存
    	        newsh = s_realloc(sh, hdrlen+newlen+1);
    	        if (newsh == NULL) return NULL;
    	        // s指向結構體的buf
    	        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;
    	        // 拷貝本來字符串到新的sds結構體的buf中
    	        memcpy((char*)newsh+hdrlen, s, len+1);
    	        // 釋放舊的sds
    	        s_free(sh);
    	        // 各個屬性從新賦值
    	        s = (char*)newsh+hdrlen;
    	        s[-1] = type;
    	        sdssetlen(s, len);
    	    }
    	    // 賦值sds新的alloc值
    	    sdssetalloc(s, newlen);
    	    return s;
    	}

SDS與C字符串的區別

總結之《Redis設計與實現》ui

C字符串 SDS
獲取字符串長度的複雜度爲O(N) 經過SDS中的len屬性,獲取字符串的複雜度爲O(1)
API是不安全的,可能會形成緩衝區溢出 API是安全的,不會形成緩衝區溢出,由於封裝的函數都會去檢查是否夠剩餘的內存地址
修改字符串長度N次必然須要執行N此內存從新分配 修改字符串長度N此最多須要執行N次內存分配,由於在字符串拼接等操做中,封裝的函數會給SDS分配預留的內存空間,因此下次操做並不必定會引發內存從新分配。
只能保存文本數據 能夠保存文本或二進制數據,由於SDS是經過len屬性來判斷字符串是否結束,而不是經過'\0'
可使用全部<string.h>庫中的函數 可使用部分<string.h>庫中的函數,由於SDS始終會將字符串結束符'\0'追加到字符串的末尾

SDS API

參考之《Redis設計與實現》this

函數 做用 時間複雜度
sds sdsnewlen(const void *init, size_t initlen) 經過C字符串init和字符串長度initlen建立一個SDS字符串 O(N), N爲長度initlen
sds sdsnew(const char *init) 經過C字符串init建立一個SDS字符串,其實是調用sdsnewlen() O(N), N爲字符串init長度
sds sdsempty(void) 建立一個空的SDS字符串,實際上調用sdsnewlen("", 0) O(1)
sds sdsdup(const sds s) 建立給定的SDS字符串s的副本(複製),實際上調用sdsnewlen(s, sdslen(s)) O(N)
void sdsfree(sds s) 釋放給定的SDS O(N)
sds sdsgrowzero(sds s, size_t len) 將sds增加到指定的長度,若是指定的長度小於sds當前長度,則不進行操做 O(N)
sds sdscatlen(sds s, const void *t, size_t len) 將指定長度len的二進制安全字符t追加到sdss O(N)
sds sdscat(sds s, const char *t) 將指定字符串t追加到sdss上,實際上調用的是sdscatlen(s, t, strlen(t)) O(N)
sds sdscatsds(sds s, const sds t) 將指定sds字符串t追加到sdss上,實際上調用的是sdscatlen(s, t, sdslen(t)) O(N)
sds sdscpylen(sds s, const char *t, size_t len) 破壞性的修改sdss,將指定長度len的二進制安全字符串t賦值給s O(N)
sds sdscpy(sds s, const char *t) 將指定的C字符串t賦值給sdss O(N)
sds sdscatvprintf(sds s, const char *fmt, va_list ap) 將不定參apfmt中格式化爲字符串,而後拼接到sdss
sds sdscatprintf(sds s, const char *fmt, ...) 將按fmt格式化後的字符串附加到sdss
sds sdscatfmt(sds s, char const *fmt, ...) 相似於sdscatprintf()函數,但速度更快,不依賴於libc實現的sprintf(); %s - C String,%S - SDS string,%i - signed int,%I - 64 bit signed integer (long long, int64_t),%u - unsigned int,%U - 64 bit unsigned integer (unsigned long long, uint64_t),%% - Verbatim "%" character
sds sdstrim(sds s, const char *cset) 從sdss左邊和右邊刪除cset中含有的字符,知道遇到非匹配時中止。 O(N)
void sdsrange(sds s, ssize_t start, ssize_t end) 保留sdss給定區間內的數據,不在區間內的數據將會被覆蓋或清除 O(N)
void sdsupdatelen(sds s) 將sdss的長度設置爲strlen(s)的長度,即遇到第一個'\0'字符的長度 O(N)
void sdsclear(sds s) 清空sdss中的數據,實際操做是將sds中的len設爲0,並將s[0]設爲'\0' O(1)
int sdscmp(const sds s1, const sds s2) 對比兩個sds字符串s1s2是否相同,實現上先對比長度,再對比內容 長度相同時O(N),長度不一樣時O(1)
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) 使用字符串sep分割sdssseplensep的長度,count是分割後返回的sds字符串個數
void sdsfreesplitres(sds *tokens, int count) 釋放sdssplitlen()返回的結果的內存,tokens爲返回的結果,count爲返回的sds字符串個數
void sdstolower(sds s) 將sdss中的全部字符轉爲小寫 O(N)
void sdstoupper(sds s) 將sdss中的全部字符轉爲大寫 O(N)
sds sdsfromlonglong(long long value) 經過一個long long類型的value來建立sds字符串,性能比sdscatprintf(sdsempty(),"%lld\n", value)要好
sds sdscatrepr(sds s, const char *p, size_t len) 將指定長度len的字符串p附加到sdss中,但要檢查p中的字符,若是是非打印字符,則要轉成\n\r\a...."\x<hex-number>"的形式 O(N)
sds *sdssplitargs(const char *line, int *argc) 將命令行參數解析成sds數組,argc表示數組大小;返回的sds數組要使用sdsfreesplitres()函數釋放內存
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) sfrom字符集的字符映射成to中的對應字符集,setlen表示fromto中字符集的個數,兩者必須嚴格一一對應
sds sdsjoin(char **argv, int argc, char *sep) 將C的字符串數組合併爲sds字符串,argv爲字符串數組的首地址,argc爲數組的長度,sep爲分隔符
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) 將sds字符串數組合併爲sds字符串,argv爲字符串數組的首地址,argc爲數組的長度,sep爲分隔符
相關文章
相關標籤/搜索