介绍
redis在设计过程中没有传统的c语言字符串,而是构建了一种名为简单动态字符串的抽象类型,并将SDS用作redis的默认字符串表示。
在redis 内部,仅有部分日志打印的地方使用了传统的字符串
SDS的定义
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};
- 属性值len,表示这个SDS字符串代表的字符串的长度
- free 表示为分配未使用的字符串长度
- buf属性是一个char类型的字符。如果存在值在,则字符以\0结束。
字符串创建
//实际创建SDS
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
//有初始化字符串事
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
// 默认初始化为0
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);
// 保证buf 为c语言字符串
sh->buf[initlen] = '\0';
//返回的buf的地址,在后续使用的时候需要转换地址
return (char*)sh->buf;
}
// 构建一个字符串
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
获取字符串长度
// o(1)时间复杂度
static inline size_t sdslen(const sds s) {
//在上面创建字符串的时候可以发现,实际返回的是buf的地址,所以在求取长度的时候需要转为结构体sds的地址,将地址超前移动sizeof(struct sdshdr)即可
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
字符串重新分配 append命令实现
/* 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;
}
/* Append the specified null termianted C string to the 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 sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
/* Append the specified sds 't' to the existing sds 's'.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
/* redis sds字符串预分配机制的实现
*
* 最多分配1M的内存,如果不超过1M 则double
*
* 不会改变字符串的长度,只是会预分配内存
*/
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);
//SDS_MAX_PREALLOC 为1024 * 1024
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;
}
// 获取分配未使用的内存
static inline size_t sdsavail(const sds s) {
// 转换地址
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
缩短字符串
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > start && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (sh->buf != sp) memmove(sh->buf, sp, len);
sh->buf[len] = '\0';
sh->free = sh->free+(sh->len-len);
sh->len = len;
return s;
}
好处
- 兼容c字符串,c语言相关的方法都可以直接使用例如strlen strcpy strcat等
- 预分配内存,频繁修改内存带来的内存重新分配开销
- 惰性删除,在缩短字符串时保留预分配的内存,在添加内容可以直接使用
- 二进制安全,所有的读取都是以len为准则,而不是通过遍历字符串的方式实现的