动态字符串
构造
redis使用SDS作为默认的字符串表示。
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
len表示字符串的长度。
free表示剩余空间的大小。
buf表示数据的空间。
SDS与C字符串的区别
- len不包括最后的’\0’。C语言中的字符数组来表示长度N的字符串,字符数组最后一个元素总是为’\0‘。
- strlen函数调用是直接调用的len字段的大小,所以时间复杂度为O(1),而C语言字符串是要遍历数组,直到遇到字符串结尾的空字符,时间复杂度为O(n)。
- 使用C语言中的strcat函数时,如果未分配足够的内存空间,就可能会发生缓冲区溢出。redis中使用sdscat去拼接。
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
/*
* 将长度为 len 的字符串 t 追加到 sds 的字符串末尾
*
* 返回值
* sds :追加成功返回新 sds ,失败返回 NULL
*
* 复杂度
* T = O(N)
*/
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
// 原有字符串长度
size_t curlen = sdslen(s);
// 扩展 sds 空间
// T = O(N)
s = sdsMakeRoomFor(s,len);
// 内存不足?直接返回
if (s == NULL) return NULL;
// 复制 t 中的内容到字符串后部
// T = O(N)
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len);
// 更新属性
sh->len = curlen+len;
sh->free = sh->free-len;
// 添加新结尾符号
s[curlen+len] = '\0';
// 返回新 sds
return s;
}
该函数会对空间进行判断,若内存不足则会返回null,否则就会对字符串进行拼接。
-
C语言中每次都会将字符串最后一个作为’\0‘,会有进行内存重新分配操作,如果在进行拼接和截断操作时,没有对内存进行操作,可能会发生缓冲区溢出和内存泄漏。redis的数据需要被频繁修改,所以需要减少内存重新分配。
- 空间预分配:如果sds的长度小于1MB,程序会分配和len属性同样大小的未使用空间,即free空间。若大于1MB,则会分配1MB的未使用空间。扩展的时候,如果未使用空间足够,则会使用为使用空间,不需要进行内存重分配。
- 惰性空间释放:sds需要缩短sds保存的字符串时,不会立即使用内存重分配,但是会用free记录下来,也就是会作为未使用空间进行保留下来。
-
二进制安全。
-
兼容部分C字符串函数。