Redis的簡單動態字符串實現

Redis 沒有直接使用 C 語言傳統的字符串表示(以空字符結尾的字符數組,如下簡稱 C 字符串), 而是本身構建了一種名爲簡單動態字符串(simple dynamic string,sds)的抽象類型, 並將sds用做 Redis 的默認字符串表示。c++

sds簡單動態字符串數據結構以下:git

1 typedef char *sds;
2 
3 struct sdshdr {
4     int len;
5     int free;
6     char buf[];
7 };

len記錄字符串的長度,free記錄sds還剩餘的空間,buf指向存儲字符的空間。redis

對應的內存空間以下圖:編程

例如最開始要存放字符串「chenrancc」:數組

通常開始的時候會比初始字符串多申請一個長度的空間放\0,如上圖所示,對應的函數是sdsnewlen
刪除後面的cc字符後:數據結構

刪除後面的cc字符後,空出兩個字符空間並不會回收,而是用free來記錄。若是要回收者兩個空閒的空間,必須從新分配一個新的sds,作法是將原來的sds經過realloc從新分配成新的sds,對應的函數爲sdsRemoveFreeSpace。若是要增長sds的空間,也是用一樣的方法經過realloc從新分配一個新的sds,對應的函數是sdsMakeRoomFor編程語言

要回收sds所在的內存空間,能夠經過函數sdsfree,它實際調用的是free函數。ide

除了上面提到的函數,sds中還定義了不少其它的函數來方便上層使用:函數

 1 sds sdsnewlen(const void *init, size_t initlen);  //用長度爲initlen的字符串建立sds
 2 sds sdsempty(void);  //建立一個長度爲0的sds
 3 sds sdsnew(const char *init);  //用null結尾的字符串建立sds
 4 sds sdsdup(const sds s);  //拷貝一個sds
 5 void sdsfree(sds s);  //釋放sds所佔的內存空間
 6 void sdsupdatelen(sds s);  //更新sds中的len爲實際的字符串長度
 7 void sdsclear(sds s);  //將sds中的字符串爲空串
 8 sds sdsMakeRoomFor(sds s, size_t addlen);  //sds字符串所佔空間增長addlen個字符(包括free所佔的字符)
 9 sds sdsRemoveFreeSpace(sds s);  //去除sds中空閒的空間
10 size_t sdsAllocSize(sds s);  //獲取sds實際佔用空間的大小
11 void sdsIncrLen(sds s, int incr);  //sds實際字符串的長度增長incr
12 sds sdsgrowzero(sds s, size_t len);  //將sds所佔的空間增長到len,增長的空間都清零
13 sds sdscatlen(sds s, const void *t, size_t len);  //sds末尾鏈接一個長度爲len的字符串
14 sds sdscat(sds s, const char *t);  //sds末尾鏈接一個以null結尾的字符串
15 sds sdscatsds(sds s, const sds t);  //sds末尾鏈接另外一個sds
16 sds sdscpylen(sds s, const char *t, size_t len);  //拷貝長度爲len的字符串到sds中
17 sds sdscpy(sds s, const char *t);  //拷貝以null結尾的字符串到sds中
18 sds sdscatvprintf(sds s, const char *fmt, va_list ap);  //sds末尾鏈接一個由可變參數造成的字符串
19 sds sdscatprintf(sds s, const char *fmt, ...);  //sds末尾鏈接一個由可變參數造成的字符串
20 sds sdstrim(sds s, const char *cset);  //去除sds字符串的先後字符,這些字符都是在cset中出現過的
21 void sdsrange(sds s, int start, int end);  //獲取sds字符串的一個字串,start和end能夠爲負數,負數表示從後面往前面索引
22 void sdstolower(sds s);  //將sds字符串中的字符設置爲小寫
23 void sdstoupper(sds s);  //將sds字符串中的字符設置爲大寫
24 int sdscmp(const sds s1, const sds s2);  //比較兩個字符串的大小
25 sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);  //用字符串sdp分割一個sds爲多個sds
26 void sdsfreesplitres(sds *tokens, int count);  //釋放由函數sdssplitlen返回的sds數組空間
27 sds sdsfromlonglong(long long value);  //將long long類型的數字轉化爲一個sds
28 sds sdscatrepr(sds s, const char *p, size_t len);  //sds末尾鏈接一個長度爲len的字符串,而且將其中的不可打印字符顯示出來
29 int is_hex_digit(char c);  //判斷一個字符釋放爲16進制數字
30 int hex_digit_to_int(char c);  //將一個16進制數字轉化爲整數
31 sds *sdssplitargs(const char *line, int *argc);  //將一行文本分割成多個參數,每一個參數能夠用類編程語言 REPL格式,若是空格,\n\r\t\0等做爲分隔符
32 sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) //將sds中出如今from中的字符替換爲to對應的字符
33 sds sdsjoin(char **argv, int argc, char *sep);  //將多個字符串用分割符鏈接起來組成一個sds
View Code

sds和c++中的vector很相似,惟一不一樣的是vector在空間不夠的時候能夠自動增長2倍的空間。spa

瞭解了sds的實現,想一想爲何redis非要本身實現一個字符串,而不是使用c所支持的字符串和相關的操做呢?

相比c語言中的字符串,sds有以下的好處:

  • 記錄了字符串的長度,用O(1)的時間複雜度能夠得到字符串的長度。
  • 有效的管理字符串所佔用的空間,自動擴展空間等。
  • 有效的防止內存越界,由於若是空間不夠,sds的相關函數會自動擴展空間。
相關文章
相關標籤/搜索