redis源碼分析(3)sds

sds是redis中用來處理字符串的數據結構。sds的定義在sds.h中:redis

1 typedef char *sds;

簡潔明瞭!簡明扼要!(X,玩我呢是吧!這特麼不就是c中的字符串麼?!)。像redis這種高端大氣上檔次的服務器顯然不會這麼的幼稚。在sds的定義以後,還有一個結構體:數組

1 struct sdshdr {
2     int len;
3     int free;
4     char buf[];
5 }

有len,有free,這就有點意思了。很明顯,根據這個結構體的定義,這是sds的header,用來存儲sds的信息。注意最後的buf定義,這個buf數組沒有設置長度。這是爲神馬呢?在gcc中,這種方式能夠使得buf成爲一個可變的數組,也就是說,能夠擴展buf同時又保證在使用的時候,感受buf始終在struct sdshdr中。有點囉嗦,其實能夠用下圖展現:服務器

  sdshdr       sds
    |           |
    V           V
    ----------------------------
    |len | free | buf …        |
    ----------------------------     

這個就是sds的內存分佈圖。struct sdshdr這個結構體放在了真正的數據以前,且是緊挨着的。這樣,經過buf引用的數組其實就是後面的數據。這個是利用了c中數組訪問的特色。
下面咱們來看看如何建立一個sds:數據結構

 1 /* Create a new sds string with the content specified by the 'init' pointer
 2  * and 'initlen'.
 3  * If NULL is used for 'init' the string is initialized with zero bytes.
 4  *
 5  * The string is always null-termined (all the sds strings are, always) so
 6  * even if you create an sds string with:
 7  *
 8  * mystring = sdsnewlen("abc",3");
 9  *
10  * You can print the string with printf() as there is an implicit \0 at the
11  * end of the string. However the string is binary safe and can contain
12  * \0 characters in the middle, as the length is stored in the sds header. */
13 sds sdsnewlen(const void *init, size_t initlen) {
14     struct sdshdr *sh;
15 
16     if (init) {
17         sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
18     } else {
19         sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
20     }
21     if (sh == NULL) return NULL;
22     sh->len = initlen;
23     sh->free = 0;
24     if (initlen && init)
25         memcpy(sh->buf, init, initlen);
26     sh->buf[initlen] = '\0';
27     return (char*)sh->buf;
28 }

重點是這句(zcalloc也同樣,只是分配內存的時候順帶初始化爲0):socket

1 sh = zmalloc(sizeof(struct sdshdr)+initlen+1)

建立一個sds的時候,實際申請的內存大小爲sdshdr的大小,加上調用者但願的sds的大小,再加一。另外,zmalloc的返回值直接賦值給了sh,sh是struct sdshdr。那麼,在建立一個sds的時候,將sds的struct sdshdr放到了真正的數據的前面,這樣能夠經過buf引用到後面的數據。多加一個一是爲了保證有地方放'\0'。根據註釋,sds默認以'\0'結尾,且能夠存放二進制的數據,由於struct sdshdr中存放了數據的長度。在sdsnewlen的最後,返回的是(char\*)sh->buf,也就是說sds實際指向的就是一個char\*數組。**全部能夠對char\*的操做也同時能夠操做sds**。函數

那sds的長度等信息如何獲取呢?看下面的代碼:this

1 static inline size_t sdslen(const sds s) {
2     struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
3     return sh->len;
4 }
5 
6 static inline size_t sdsavail(const sds s) {
7     struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
8     return sh->free;
9 }

 

這兩個函數分別是獲取sds的實際長度和可用空間。核心代碼就是這句:spa

1 struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));

將sds的地址減去struct sdshdr的長度而後賦值給sh,這就獲得了sds對應的struct sdshdr。根據前面的內存分佈圖,struct sdshdr始終是在數據的前面,一次很容易獲得struct sdshdr的地址。獲得了struct sdshdr的地址以後,其餘的就很簡單了。code

sds支持動態的擴展空間,sdsMakeRoomFor這個函數用來擴展sds的空間:blog

 1 /* Enlarge the free space at the end of the sds string so that the caller
 2  * is sure that after calling this function can overwrite up to addlen
 3  * bytes after the end of the string, plus one more byte for nul term.
 4  * 
 5  * Note: this does not change the *length* of the sds string as returned
 6  * by sdslen(), but only the free buffer space we have. */
 7 sds sdsMakeRoomFor(sds s, size_t addlen) {
 8     struct sdshdr *sh, *newsh;
 9     size_t free = sdsavail(s);
10     size_t len, newlen;
11 
12     if (free >= addlen) return s;
13     len = sdslen(s);
14     sh = (void*) (s-(sizeof(struct sdshdr)));
15     newlen = (len+addlen);
16     if (newlen < SDS_MAX_PREALLOC)
17         newlen *= 2;
18     else
19         newlen += SDS_MAX_PREALLOC;
20     newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
21     if (newsh == NULL) return NULL;
22 
23     newsh->free = newlen - len;
24     return newsh->buf;
25 }

這個函數保證sds至少有addlen長度的空間可用。這個函數體現了sds的空間擴展策略。若是有足夠的空間,則直接返回。若是空間不夠,當len+addlen小於SDS_MAX_PREALLOC時,將空間擴展到(len+addlen)\*2。當len+addlen大於SDS_MAX_PREALLOC,將空間擴展到len+addlen+SDS_MAX_PREALLOC。sds的擴展考慮了實際須要的空間大小,擴展的效率要高一些。若是每次擴大原來的二倍,當須要的空間大於初始空間二倍時,須要屢次的擴展操做,也就意味着屢次的zrealloc操做。sds的擴展能夠在任何狀況下一次擴展到位。


sds最大的特色就是全部能夠對char\*的操做均可以操做sds,這在實際使用sds的的時候能夠帶來不少方便。好比,從socket中讀取數據存儲到sds中,能夠以下操做:

1 /* sds s */
2 int oldlen = sdslen(s);
3 s = sdsMakeRoomFor(s, BUFFER_SIZE);
4 nread = read(fd, s+oldlen, BUFFER_SIZE);
5 sdsIncrLen(s, nread);

在調用read的時候,能夠把sds看作是char\*來處理(實際上sds就是char\*)。固然,最後必定要調用sdsIncrLen來修正sds的長度。

相關文章
相關標籤/搜索