翻redis 源码的时候,发现有些用法真是很巧妙的,一个是指针变换,一个是内存管理策略,当然后者是有利弊的。
sds.h 就这两个数据类型,这里,能够sds 和 sdshdr 相互转化。对外的接口,参数和返回只有char * 。redis 为每个char * 都维护了个数据结构 sdshdr ,防止溢出,又可以减少内存分配。这里char * 怎么跟 sdshdr 关联上?
typedef char *sds;
struct sdshdr {
long len;
long free;
char buf[];
};
这里 sdshdr 是16个字节,buf 是动态数组,计算偏移的时候size 为0。通过sdshdr 取 buf 地址,强制转成 char * ,也就是sds ,这个数组转字符串指针的操作很好理解。当我们获得sds 的时候,想知道预分配长度,直接向左偏移struct 大小,就是sdshdr的地址,强制转换类型后,就额可以取len。
看源码 sdslen 函数就可以看出:
size_t sdslen(const sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); //先偏移struct大小,强制转成sdshdr
return sh->len;
}
第二个有意思的是sds 的内存预分配,在对字符串拼接的时候,有个惰性预分配的操作,每次free 为0 ,触发重新拷贝,都将多分配一倍多余内存,备用。这里好处是减少内存分配,当然也浪费了内存。对应底层函数如下:
static 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)*2; //内存空间在要重新分配的情况下,直接加倍
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
#ifdef SDS_ABORT_ON_OOM
if (newsh == NULL) sdsOomAbort();
#else
if (newsh == NULL) return NULL;
#endif
newsh->free = newlen - len;
return newsh->buf;
}