Redis學習筆記——SDS

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設計與實現》對象

相關文章
相關標籤/搜索