Redis没有使用C语言的字符串结构,而是自己设计了一个简单的动态字符串结构sds(simple dynamic string)。Redis中关于sds的实现主要在sds.c和sds.h中
1. sds数据结构定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
typedef char *sds;
struct sdshdr { int len; int free; char buf[]; };
|
buf[]为长度为0的数据,不占用内存,sizeof(struct sdshdr)返回8。使用char buf[]而不是char *buf,可以避免二次分配内存,同时buf[]不占用空间,也能节省内存。
2. sds创建函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| sds sdsnewlen(const void *init, size_t initlen) { struct sdshdr *sh; if (init) sh = zmalloc(sizeof(struct sdshdr) + initlen + 1); } else { sh = zcalloc(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. sds释放函数
1 2 3 4 5
| void sdsfree(sds s) { if (s == NULL) return; zfree(s - sizeof(struct sdshdr)); }
|
4. sds扩容函数
对 sds 中 buf 的长度进行扩展,确保在函数执行之后,buf 至少会有 addlen + 1 长度的空余空间(额外的 1 字节是为 \0 准备的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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 = zrealloc(sh, sizeof(struct sdshdr) + newlen + 1);
if (newsh == NULL) return NULL;
newsh->free = newlen - len;
return newsh->buf; }
|
5. sds回收函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| sds sdsRemoveFreeSpace(sds s) { struct sdshdr *sh;
sh = (void *) (s - (sizeof(struct sdshdr)));
sh = zrealloc(sh, sizeof(struct sdshdr) + sh->len + 1);
sh->free = 0;
return sh->buf; }
|
6. sds拼接函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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; }
|
7. sds与C中字符串区别
- C语言使用长度N+1的字符数组表示长度为N的字符串,使用’\0’作为字符串的结尾
- C字符串不记录字符串长度,strlen操作复杂度为O(N),而sds取长度操作为O(1)
- C语言字符串使用不当容易造成缓冲区溢出,而sds通过free变量控制。
- sds扩容是不是按照字符串所占用字节大小严格扩容的,而是会额外分配一些暂时使用不到的空间。通过这种策略可以一定程度上减少字符串增长所需的内存重分配次数。
- C语言字符串以’\o’结尾,字符串中不能含有空字符,sds通过len来判断字符串结尾,可以用来存一些具有特殊格式要求的二进制数据。