SDS的实现
SDS即简单动态字符串,为Redis的几大基本数据结构之一,有广泛的用途
基本函数总览
函数 | 功能 | 复杂度 |
---|---|---|
sdsnewlen | 由给定字符串创建SDS | O(N) |
sdslen | 返回字符串长度 | O(1) |
sdsdup | 复制一个SDS | O(N) |
sdsfree | 释放字符串空间 | O(1) |
sdsavail | 获取空闲空间大小 | O(1) |
sdsMakeRoomFor | 扩展SDS buf空间 | O(N) |
sdsgrowzero | 扩展字符串到指定长度,多的用0填充 | O(N) |
sdscatlen | 拼接字符串到已有字符串末尾 | O(N) |
sdscpylen | 将字符串拷贝到现有SDS处 | O(N) |
… | … | … |
sdshdr结构体
/*
* 类型别名,用于指向 sdshdr 的 buf 属性
*/
typedef char *sds;
/*
* 保存字符串对象的结构,显然32位下 sizeof(sdshdr) = 8
*/
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
sds的结构如图
sdsnewlen
/*
* 创建init指向的长度为 initlen 的字符串
*/
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
// 根据是否有初始化内容,选择适当的内存分配方式
// T = O(N)
if (init) {
// zmalloc 不初始化所分配的内存
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
// zcalloc 将分配的内存全部初始化为 0
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
// 内存分配失败,返回
if (sh == NULL) return NULL;
// 设置初始化长度
sh->len = initlen;
// 新 sds 不预留任何空间
sh->free = 0;
// 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
// T = O(N)
if (initlen && init)
memcpy(sh->buf, init, initlen);
// 以 \0 结尾
sh->buf[initlen] = '\0';
// 返回 buf 部分,而不是整个 sdshdr
return (char*)sh->buf;
}
sdslen
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
sdsdup
sds sdsdup(const sds s) {
return sdsnewlen(s, sdslen(s));
}
sdsfree
void sdsfree(sds s) {
if (s == NULL) return;
zfree(s-sizeof(struct sdshdr));
}
zfree
在之前内存分配中已介绍
sdsavail
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
sdsMakeRoomFor
/*
* 对 sds 中 buf 的长度进行扩展,确保在函数执行之后
* buf 至少会有 addlen + 1 长度的空余空间
* 返回值sds :扩展成功返回扩展后的 sds,扩展失败返回 NULL
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
// 获取 s 目前的空余空间长度
size_t free = sdsavail(s);
size_t len, newlen;
// s 目前的空余空间已经足够,无须再进行扩展,直接返回
if (free >= addlen) return s;
// 获取 s 目前已占用空间的长度
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
// s 最少需要的长度
newlen = (len+addlen);
// 根据新长度,为 s 分配新空间所需的大小
if (newlen < SDS_MAX_PREALLOC)
// 如果新长度小于 SDS_MAX_PREALLOC
// 那么为它分配两倍于所需长度的空间
newlen *= 2;
else
// 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC(1024 * 1024)
newlen += SDS_MAX_PREALLOC;
// T = O(N)
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
// 内存不足,分配失败,返回
if (newsh == NULL) return NULL;
// 更新 sds 的空余长度
newsh->free = newlen - len;
// 返回 sds
return newsh->buf;
}
sdsgrowzero
/*
* 将 sds 扩充至指定长度,未使用的空间以 0 填充,如果给定长度比已有长度小,直接返回
* 返回值sds :扩充成功返回新 sds ,失败返回 NULL
*/
sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;
// 如果 len 比字符串的现有长度小,
// 那么直接返回,不做动作
if (len <= curlen) return s;
s = sdsMakeRoomFor(s,len-curlen);
// 如果内存不足,直接返回
if (s == NULL) return NULL;
/* Make sure added region doesn't contain garbage */
// 将新分配的空间用 0 填充,防止出现垃圾内容
// T = O(N)
sh = (void*)(s-(sizeof(struct sdshdr)));
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
// 更新属性
totlen = sh->len+sh->free;
sh->len = len;
sh->free = totlen-sh->len;
// 返回新的 sds
return s;
}
sdscatlen
/*
* 拼接新字符串到已有字符串末尾
*/
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 中的内容到字符串后部
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;
}
sdscpylen
sds sdscpylen(sds s, const char *t, size_t len) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
// sds 现有 buf 的长度
size_t totlen = sh->free+sh->len;
// 如果 s 的 buf 长度不满足 len ,那么扩展它
if (totlen < len) {
// T = O(N)
s = sdsMakeRoomFor(s,len-sh->len);
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
totlen = sh->free+sh->len;
}
// 复制内容
// T = O(N)
memcpy(s, t, len);
// 添加终结符号
s[len] = '\0';
// 更新属性
sh->len = len;
sh->free = totlen-len;
// 返回新的 sds
return s;
}
小结
在一段连续内存中,前面存储sdshdr
头部,包含了字符串长度和空闲空间大小,后面存储具体的字符串。这样获取字符串长度为O(1),实现简单,具有借鉴意义