Redis定義了一種數據結構動態字符串來表示字符串值,該數據結構的定義在文件/src/sds.h中算法
/* * 保存字符串對象的結構 */ struct sdshdr { // buf 中已佔用空間的長度 int len; // buf 中剩餘可用空間的長度 int free; // 數據空間 char buf[]; };
變量名已經清晰的記錄了變量的做用。初次以外,還定義了這個結構的一些操做接口。數組
static inline size_t sdsavail(const sds s); //返回字符串生於的可用空間的長度,也就是free值 sds sdsnewlen(const void *init, size_t initlen); //根據init指向的字符串常量,和initlen指定的大小來構造字符串 sds sdsnew(const char *init); //根據init指向的字符串常量來構造字符串,不過是經過sdsnewlen來實現的sds sdsempty(void); // 建立長度爲0的空字符串size_t sdslen(const sds s); //返回字符串實際佔用空間的長度,也就是len值 sds sdsdup(const sds s); //拷貝一個字符串,也是經過sdsnewlen來實現 sds sdsfree(sds s); //釋放字符串佔用的空間 sds sdsgrowzero(sds s, size_t len); //將sds擴充至指定長度,末尾未使用的空間以0填充 sds sdscatlen(sds s, const void *t, size_t len); //將字符串t的前len個字節填充到s的末尾 sds sdscat(sds s, const char *t); //將字符串t填充到s的末尾,動下腦子就能猜到,內部經過sdscatlen實現 sds sdscatsds(sds s, const sds t); //同上,由於typedefchar *sds; sds sdscpylen(sds s, const char *t, size_t len); //將t的前len個字符拷貝到s上,也就是會覆蓋s的內容 sds sdscpy(sds s, const char *t); // 將t的內容拷貝到s上 sds sdscatvprintf(sds s, const char *fmt, va_list ap);//經過fmt指定個格式來格式化字符串 sds sdscatfmt(sds s, char const *fmt, ...); //將格式化後的任意數量個字符串追加到s的末尾,經過sdscatvprintf實現 sds sdstrim(sds s, const char *cset); //對s的左右兩端進行裁剪,去掉cset指定的字符 void sdsrange(sds s, int start, int end); //經過索引區間[start,end]來截取字符串 void sdsupdatelen(sds s); //根據字符串所佔用空間的長度大小來更新len、free void sdsclear(sds s); //將字符串的第一個字符串置爲'\0',也就是把字符串置爲空字符串,可是沒有釋放空間 int sdscmp(const sds s1, const sds s2); //比較兩個sds是否相等 sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); //使用分隔符sep對s進程進行分割,返回一個sds數組,同時count設置爲數組的個數 //len和seplen分別是s和sep的長度 void sdsfreesplitres(sds *tokens, int count);//釋放數組tokens的count個sds void sdstolower(sds s); // 將sds的全部字符都轉換成小寫 void sdstoupper(sds s); // 將sds的全部字符都轉換成大寫 sds sdsfromlonglong(long long value); //將長整型數據轉成字符串 sds sdscatrepr(sds s, const char *p, size_t len); //將長度爲len的字符串p以帶引號的格式追加到s的末尾 //如 s = "abc" , p = "gbdf\n134"; 那麼函數的返回結果爲 ret = "abc\"gbdf\\n134\"" sds *sdssplitargs(const char *line, int *argc);//將一行文本分割成多個參數,參數的個數存在argc sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); // 將字符串s中,出現存在from中指定的字符,都轉換成to中的字符,from與to是有位置關係, // 假如from = "ckj", to = "345", 那麼‘c’就換成‘3’, 'k'就換成‘4’, 以此類推 sds sdsjoin(char **argv, int argc, char *sep); //經過分隔符sep把字符數組argv拼接成一個字符串 sds sdsMakeRoomFor(sds s, size_t addlen); //對字符串進行擴充,使之有addlen+1個長度的剩餘空間 void sdsIncrLen(sds s, int incr); //在不從新分配空間的基礎上,給字符串增長incr長度 sds sdsRemoveFreeSpace(sds s); //回收sds剩餘的空間內容,可是不會修改字符串的內容 size_t sdsAllocSize(sds s); //返回給s分配的內存的字節數
代碼中操做的對象是sds,而且屢次利用sdshr中buf的偏移地址來獲取sdshr的地址,以下安全
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
我的感受這個能夠寫成一個宏,像offsetof。之因此能夠這樣用sizeof(struct sdshdr)
是由於,buf是變長數組,由於沒有指定長度,因此沒有佔用空間。因此,sizeof(struct sdshdr) == sizeof(int) + sizeof(int)
1.Redis經過空間預分配來減小修改字符串帶來的內存從新分配的開銷。分配的算法以下:
A. 若是對 SDS 進行修改以後, SDS 的長度(也便是 len 屬性的值)將小於 1 MB , 那麼程序分配和 len 屬性一樣大小的未使用空間, 這時 SDS len 屬性的值將和 free 屬性的值相同。 舉個例子, 若是進行修改以後, SDS 的 len 將變成 13 字節, 那麼程序也會分配 13 字節的未使用空間, SDS 的 buf 數組的實際長度將變成 13 + 13 + 1 = 27 字節(額外的一字節用於保存空字符)。
B. 若是對 SDS 進行修改以後, SDS 的長度將大於等於 1 MB , 那麼程序會分配 1 MB 的未使用空間。 舉個例子, 若是進行修改以後, SDS 的 len 將變成 30 MB , 那麼程序會分配 1 MB 的未使用空間, SDS 的 buf 數組的實際長度將爲 30 MB + 1 MB + 1 byte數據結構
2.Redis縮短字符串時,只把字符串的第一個字符置爲'0',不回收空間。也就是所謂的惰性空間釋放函數
3.Redis字符串是二進制安全的,由於sdshr裏的字符串數組 char buf[] 存儲字符的二進制數據,經過len來表示大小設計
最後附上《Redis設計與實現的總結》裏指出的C字符串跟SDS的區別
字符串和 SDS 之間的區別
C 字符串 SDS
獲取字符串長度的複雜度爲 O(N) 。 獲取字符串長度的複雜度爲 O(1) 。
API 是不安全的,可能會形成緩衝區溢出。 API 是安全的,不會形成緩衝區溢出。
修改字符串長度 N 次必然須要執行 N 次內存重分配。 修改字符串長度 N 次最多須要執行 N 次內存重分配。
只能保存文本數據。 能夠保存文本或者二進制數據。
可使用全部 <string.h> 庫中的函數。 可使用一部分 <string.h> 庫中的函數。code
參考:《Redis設計與實現》對象