標籤:資料結構 源碼 redis 開源項目
國慶節除了陪伴呂友,花了差不多兩天時間初步瞭解了一下Redis,接下來一段時間,將深入閱讀Redis源碼,也做一個記錄,看一個源碼,我覺得還是先從基礎的模組看起,比如,資料結構,Redis中的實現了很多的資料結構,當然,很多開源項目也是自己實現各種資料結構以滿足定製需求,我首先閱讀的是底層字串模組-SDS
sds的定義:
typedef char *sds;
說白了就是字串的別名,整個Redis的字串用的就是這個,但是內部使用了一個結構來維護這個sds
struct sdshdr { unsigned int len; unsigned int free; char buf[]; };
之所以這樣設計,是為了符合Redis的某一些特性,總的來說,sds相比於C語言的字串具有以下幾個優點:
1.擷取字串長度的複雜度為O(1),因為內部維護了一個儲存字串長度的len
static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->len;}static inline size_t sdsavail(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->free;}
要想訪問到len和free欄位,還要把指標直到結構體開始出,當時看到這裡還猶豫了一下,基礎知識不行— —!
2.是二進位安全的
這裡要解釋一下什麼是二進位安全,C語言中的字串採用ASCII編碼錶示,但是這裡我們用的是一個字元數組,怎麼存進去的,拿出來還是什麼,例如:test\0test\0,那麼len就會是9,讀出來也是test\0test\0,而不是說讀到第一個結束符就結束了
sds s = sdsnewlen("test", 5); printf("%s\n", s); size_t len = sdslen(s); printf("%zu\n", len); size_t free = sdsavail(s); printf("%zu\n", free);
可以得出得到len=5
源碼:
/* 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) { struct sdshdr *sh; if (init) { sh = malloc(sizeof(struct sdshdr)+initlen+1); } else { sh = calloc(1,sizeof(struct sdshdr)+initlen+1); } if (sh == NULL) return NULL; sh->len = initlen; sh->free = 0; if (initlen && init) memcpy(sh->buf, init, initlen); sh->buf[initlen] = '\0'; return (char*)sh->buf;}
3.不會出現緩衝區溢位,利用free欄位可以減少修改字串長度導致的記憶體重新分配
我們知道,C語言中的這兩組函數如果使用不當,是會造成緩衝區溢位的
char *strcat(char *dest, const char *src);char *strncat(char *dest, const char *src, size_t n);The strcat() function appends the src string to the dest string, overwriting the null byte ('\0') at the end of dest, and then adds a terminating null byte. The strings may not overlap, and the dest string must have enough space for the result.The strncat() function is similar, except that* it will use at most n characters from src; and* src does not need to be null terminated if it contains n or more characters.As with strcat(), the resulting string in dest is always null terminated.
而sds中的cat實現如下:
/* 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) { struct sdshdr *sh; size_t curlen = sdslen(s); s = sdsMakeRoomFor(s,len); if (s == NULL) return NULL; sh = (void*) (s-(sizeof(struct sdshdr))); memcpy(s+curlen, t, len); sh->len = curlen+len; sh->free = sh->free-len; s[curlen+len] = '\0'; return s;}
那麼sdsMakeRoomFor是如何?的呢?
/* 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 sdsMakeRoomFor(sds s, size_t addlen) { struct sdshdr *sh, *newsh; size_t free = sdsavail(s); size_t len, newlen; if (free >= addlen) return s; len = sdslen(s); sh = (void*) (s-(sizeof(struct sdshdr))); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1); if (newsh == NULL) return NULL; newsh->free = newlen - len; return newsh->buf;}
可以看出sds對於字串長度修改有這樣一個規則,如果
AddLen < free 使用free
AddLen > free && AddLen < 1M free = len
AddLen > free && AddLen > 1M free = 1M
Redis源碼-資料結構之sds字串