Redis 未直接使用 C 語言原生字符串(以 \0 結尾的 char*),而是構建了 **SDS(Simple Dynamic Strings)** 作為底層字符串表示。SDS 定義在 sds.h/sds.c 中,提供 O(1) 獲取長度、二進制安全、預分配空間減少內存重分配等特性,是 Redis 高性能的關鍵基礎設施之一。
sdshdrstruct sdshdr {
long len; // 已使用字節數(不含結尾 \0)
long free; // 剩餘可用字節數
char buf[]; // 柔性數組,存儲實際字符數據,末尾補 \0
};
關鍵設計:
len 與 free 分離,避免每次 strlen 掃描,實現 O(1) sdslen()。buf 使用 C99 柔性數組,結構體與數據連續分配,提高緩存局部性。\0,兼容部分 C 庫函數(如 printf("%s")),但不依賴 \0 判斷長度,故可存儲二進制數據(含嵌入 \0)。sdstypedef char *sds;
sds 實際指向 buf 數組起始位置,而非 sdshdr 結構體開頭。這是一個「指針偏移」技巧:
char*,使用極其簡便。sdshdr:sh = (void*)(s - sizeof(struct sdshdr))。sdsnewlensds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh = zmalloc(sizeof(struct sdshdr) + initlen + 1);
if (!sh) return NULL; // 或 SDS_ABORT_ON_OOM
sh->len = initlen;
sh->free = 0;
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);
else memset(sh->buf, 0, initlen);
}
sh->buf[initlen] = '\0';
return (char*)sh->buf; // 返回 buf 指針
}
分配一次性完成 header + data + \0,無額外碎片。
sdslen 與指針回溯技巧size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s - sizeof(struct sdshdr));
return sh->len;
}
同理,sdsavail 獲取 free,sdsIncrLen 修改長度等均通過此偏移訪問元數據。
| 特性 | C 字符串 | SDS |
|---|---|---|
| 獲取長度 | O(N) | O(1) |
| 二進制安全 | 否(\0 截斷) | 是 |
| 內存重分配 | 每次增長皆重分配 | 預分配 free 空間,惰性釋放 |
| API 兼容 | 原生 | 需專用 API,但可傳入 C 函數 |
sds.h 理解結構體與 sds 別名定義。sdsnewlen、sdsMakeRoomFor(空間預分配策略)、sdscat(拼接)、sdsfree(釋放時回溯 header)。zmalloc/zfree 封裝的內存統計功能。SDS 以「結構體頭部 + 柔性數組 + 指針偏移」的經典 C 語言技巧,在保持 char* 使用便利性的同時,賦予了字符串豐富的元數據與高效操作能力,是 Redis 源碼中值得深入學習的經典數據結構實現。