前言項目裡用到了redis資料結構,不想只是簡單的調用api,這裡對我的讀書筆記做一下記錄。原文地址:http://www.redisbook.com/en/latest/internal-datastruct/sds.html資料類型定義與sds實現有關的資料類型有兩個,一個是 sds:
// 字串類型的別名typedef char *sds;
另一個是 sdshdr:
// 持有sds的結構struct sdshdr {// buf中已經被使用的字串空間數量int len;// buf中預留字串的空間數量int free;// 實際儲存字串的地方char buf[];};
其中,sds只是字串數群組類型char*的別名,而sdshdr用於持有和儲存sds的資訊比如,sdshdr.len可以用於在O(1)的複雜度下擷取sdshdr.buf中儲存的字串的實際長度,而sdshdr.free則用於儲存sdshdr.buf中還有多少預留空間(這裡sdshdr應該是sds handler的縮寫)
將sdshdr用作sdssds模組對sdshdr結構使用了一點小技巧:通過指標運算,它使得sdshdr結構可以像sds類型一樣被傳值和處理,並在需要的時候恢複成sdshdr類型通過下面的函數定義來理解這個技巧sdsnewlen 函數返回一個新的sds值,實際上,它建立的卻是一個sdshdr結構:
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;// 剛開始free為0if (initlen && init) {memcpy(sh->buf, init, initlen);}sh->buf[initlen] = '\0';// 只返回sh->buf這個字串部分return (char *)sh->buf;}
通過使用變數持有一個sds的值,在遇到那些只處理sds值本身的函數時,可以直接將sds傳給它們。比如說,sdstoupper 函數就是其中的一個例子:
static inline size_t sdslen(const sds s){// 從sds中計算出相應的sdshdr結構struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr)));return sh->len;}void sdstoupper(sds s){int len = sdslen(s), j;for (j = 0; j < len; j ++)s[j] = toupper(s[j]);}
這裡有一個技巧,通過指標運算,可以從sds值中計算出相應的sdshdr結構:sds雖然是指向char *的buf(ps:並且空數組不佔用記憶體空間,數組名即為記憶體位址),但是分配的時候是分配sizeof(struct sdshdr) + initlen + 1的,通過sds - sizeof(struct sdshdr)可以計算出struct sdshdr的首地址,從而可以得到len和free的資訊sdsavail 函數就是使用這中技巧的一個例子:
static inline size_t sdsavail(const sds s){struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr)));return sh->free;}
記憶體配置函數實現和Reids 的實現決策相關的函數是 sdsMakeRoomFor :
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)));// 設定新sds的字串長度// 這個長度比完成本次拼接實際所需的長度要大// 通過預留空間最佳化下次拼接操作newlen = (len + addlen);if (newlen < 1024 * 1024)newlen *= 2;elsenewlen += 1024;// 重新分配sdshdrnewsh = realloc(sh, sizeof(struct sdshdr) + newlen + 1);if (newsh == NULL) return NULL;newsh->free = newlen - len;// 只返回字串部分return newsh->buf;}
這種記憶體配置策略表明,在對sds 值進行擴充(expand)時,總會預留額外的空間,通過花費更多的記憶體,減少了對記憶體進行重分配(reallocate)的次數,並最佳化下次擴充操作的處理速度再把redis的如果實現對sds字串擴充的方法貼一下,很不錯的思路:
/** * 按長度len擴充sds,並將t拼接到sds的末尾 */sds sdscatlen(sds s, const void *t, size_t len){struct sdshdr *sh;size_t curlen = sdslen(s);// O(N)s = sdsMakeRoomFor(s, len);if (s == NULL) return NULL;// 複製memcpy(s + curlen, t, len);// 更新len和free屬性sh = (void *)(s - (sizeof(struct sdshdr)));sh->len = curlen + len;sh->free = sh->free - len;// 終結符s[curlen + len] = '\0';return s;}/** * 將一個char數組拼接到sds 末尾 */sds sdscat(sds s, const char *t){return sdscatlen(s, t, strlen(t));}
OK,這裡暫時對sds(簡單動態字串)的學習告一段落,繼續寫商務邏輯代碼,很好奇hashs和sets結構是如何?!!