SDS的结构
以下是sds的结构
// sdshdr 结构
struct sdshdr {
// buf 已占用长度
int len;
// buf 剩余可用长度
int free;
// 实际保存字符串数据的地方
// 利用c99(C99 specification 6.7.2.1.16)中引入的 flexible array member,通过buf来引用sdshdr后面的地址,
// 详情google "flexible array member"
char buf[];
//这个数组没有大小,是所谓的柔性数组,是不占据内存大小的
};
buf指向一个字符串,保存的'\0'不算在len属性里面,通过使用sds而不是C字符串,获取长度即是O(1)的
其次,由于可能频繁修改字符串,因而防止内存的重分配对性能造成影响,sds采取空间预分配和惰性空间释放两种优化策略
- 空间预分配 如果修改后sds的长度小于1MB,那么会分配相同长度的free空间,如果大于等于1MB,那么会直接分配1MB
- 惰性空间释放 通过惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配操作,并为将来可能有的增长操作提供了优化
SDS是二进制安全的
主要体现在sds是以len来判断字符串的结尾而不是'\0',因此满足了Redis可以存储任意格式的二进制数据
SDS的函数解读
sdsnewlen
//sdsnewlen根据init字符串和initlen初始化sdsnewlen
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
// 有 init ?
// O(N)
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);//分配结构体空间和buf空间
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
// 内存不足,分配失败
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
// 如果给定了 init 且 initlen 不为 0 的话
// 那么将 init 的内容复制至 sds buf
// O(N)
if (initlen && init)
memcpy(sh->buf, init, initlen);
// 加上终结符
sh->buf[initlen] = '\0';
// 返回 buf 而不是整个 sdshdr
return (char*)sh->buf;
}
sdsnew
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
sdsempty
sds sdsempty(void) {
// O(N)
return sdsnewlen("",0);
}
sdsfree
void sdsfree(sds s) {
if (s == NULL) return;
zfree(s-sizeof(struct sdshdr));
}
/*
* 更新给定 sds 所对应的 sdshdr 结构的 free 和 len 属性
*
* T = O(n)
*/
void sdsupdatelen(sds s) {
/*
在初始化即sdsnewlen时,sh是指向buf位置的,此处减去是为了重新指回结构体的头部
*/
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
// 计算正确的 buf 长度
int reallen = strlen(s);
// 更新属性
sh->free += (sh->len-reallen);
sh->len = reallen;
}
/*
* 计算给定 sds buf 的内存长度(包括已使用和未使用的)
*
* T = O(1)
*/
size_t sdsAllocSize(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
/*
* 此处是将指针指向buf后加上len和free的长度以及'\0'
*/
return sizeof(*sh)+sh->len+sh->free+1;
}
以上是sds中常见的函数