一、SDS的定义
Redis自己构建了一种名为简单动态字符串SDS的抽象类型,并将SDS用作Redis的默认字符串表示。
typedef char *sds;
struct sdshdr {
int len;
int free;
char buf[];
};
其中,len代表存储的字符串长度,free代表buf中剩余存储空间,buf用来存储字符串。
倘若使用指针char *buf,分配内存需要两个步骤:一次分配结构体,一次分配char *buf,在释放内存的时候也要释放两次内存:一次为char *buf内存,一次为结构体内存。而使用长度为0的字符数组可以将分配和释放内存的次数都降低为1,从而简化了内存的管理。并且长度为0的数组,char buf[]占用内存空间为0。
为什么是typedef char *sds,而不是typedef struct sdshdr *sds呢?
看下面两个方法:
//返回sds实际保存的字符串长度
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
return sh->len;
}
//返回可用空间长度
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
return sh->free;
}
用 (void*)(s - (sizeof(struct sdshdr)))来获取struct sdshdr对象,再看初始化方法:
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
if (init) {
sh = zmalloc(sizeof(struct sdshdr) + initlen + 1);//加1存储'\0'
}
else {
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);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
返回的是指向buf的地址,而不是struct sdshdr的首地址,所以typedef char *sd。
其中char buf[]占用内存空间为0,可知sizeof(struct sdshdr)为8。
struct sdshdr sh = (void)(s - (sizeof(struct sdshdr)));在C++编译器中会出错,void*不能用于初始化struct sdshdr *对象。
二、空间预分配与惰性空间释放
1、空间预分配
对SDS中buf进行扩展的代码:
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);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr) + newlen);
if (newsh == NULL) return NULL;
newsh->free = newlen - len;
return newsh->buf;
}
其中,SDS_MAX_PREALLOC=1024*1024,即若对SDS修改后,其长度小于1M,则程序分配和len属性同样大小的未使用空间;若对SDS修改后,其长度不小于1M,则程序分配1M的未使用空间。
2、惰性空间释放
惰性空间释放用于优化SDS字符串缩短操作,当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是用free属性将这些字节的数量记录起来,等待将来使用。
三、二进制安全
C字符串中除了末尾之外,字符串里不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,不能保存图片、音频、视频、压缩文件等二进制数据。
SDS使用len属性的值判断字符串是否结束,而不是空字符串,因此用SDS保存二进制数据是可以的。
sds的优点:
1、在len属性中记录了SDS本身的长度,所以获取一个SDS长度的时间复杂度仅为O(1).
2、校验剩余空间大小,杜绝了缓冲区溢出。
3、空间预分配和惰性空间释放。
4、二进制存储安全
5、兼容部分C字符串函数