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; }
總結之《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' 追加到字符串的末尾 |
參考之《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) |
將不定參ap 按fmt 中格式化爲字符串,而後拼接到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字符串s1 ,s2 是否相同,實現上先對比長度,再對比內容 |
長度相同時O(N),長度不一樣時O(1) |
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) |
使用字符串sep 分割sdss ,seplen 是sep 的長度,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) |
將s 中from 字符集的字符映射成to 中的對應字符集,setlen 表示from 和to 中字符集的個數,兩者必須嚴格一一對應 |
|
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 爲分隔符 |