1、redis自定义了新的字符串机制SDS
Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组),而是自己构建了一种名为简单动态字符串SDS的抽象类型
2、简单动态字符串SDS
SDS定义:
struct sdshdr {
// buf 中已占用空间的长度,不包括末尾‘\0’
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间,一个char类型的数组
char buf[];
};
![](https://i-blog.csdnimg.cn/blog_migrate/0548472896de5a2dc03bc9de326d4bff.png)
3、SDS与C字符串的区别
![](https://i-blog.csdnimg.cn/blog_migrate/842f9f062e074933de8aaea21a29fbd8.png)
1)获取字符串长度的复杂度
![](https://i-blog.csdnimg.cn/blog_migrate/e6659e1ef40a9ba8c881630ee8593dee.png)
在C字符串中,C字符串是以一个数组来保存的(如上图所示),因此需要获取字符串的长度,需要遍历整个数组,时间复杂度为O(N);
在SDS中,由于SDS结构体中保存了一个len变量,直接读取该变量即可知道字符串的长度,时间复杂度为O(1)
2)缓冲区溢出
由于C字符串在保存之前需要为这个字符串数组分配大小,如果一直往这个数组插入元素,则会造成缓冲区溢出,当然如果要避免缓冲区溢出,则需要先检查数组是否被填满,如果是,则重新手动分配一个更大的空间,然后将数据拷贝过去,再释放原数组;
SDS中,用户只需要往里面添加字符即可,看似不会再有缓冲区溢出,其实只是将缓冲区溢出检查,重新分配内存,拷贝数据,释放原缓冲区这些操作交给幕后去执行了,而不用用户去执行这一部分工作(有点类似于STL中vector的扩容机制)
3)减小修改字符串时带来的内存重分配次数
C字符串的缺陷:
执行增长字符串的操作时,程序需要先通过内存重分配来扩展底层数组的空间大小,否则可能会产生缓冲区溢出;
执行缩短字符串的操作时,程序需要通过内存重分配来释放字符串不再使用的那部分空间,否则可能会产生内存泄漏
SDS的空间预分配,
以修改后len是否大于1M,有两种分配方式:
如果修改后len长度将小于 1 M, 这时分配给free的大小和len一样, 例如修改过后为13字节, 那么给free也是13字节 . buf实际长度变成了 13 byte+ 13byte + 1byte = 27byte;
如果修改后len长度将大于等于1 M, 这时分配给free的长度为 1 M, 例如修改过后为30M, 那么给free是1M . buf实际长度变成了 30M + 1M + 1 byte;
在修改时, 首先检查空间是不是够, 如果足够, 直接使用, 否则执行内存重分配.
SDS惰性空间释放:
程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free变量将这些字节的数量记录起来,病等待将来使用;
SDS也提供了相应的API,让我们可以在有需要的时候,真正释放SDS的未使用空间,所以不用担心惰性空间释放策略会造成内存泄漏
4)二进制安全
C字符串只能保存文本数据,因为C字符串遇到‘\0’即认为结束;SDS则是通过len变量来判断字符串结束位置的,因此SDS可以保存文本或二进制数据
5)SDS仅可以使用部分C字符串函数
SDS在字符串末尾添加‘\0’,主要是为了能够让那些保存文本数据的SDS可以重用一部分C字符串处理函数,而不用重写
4、扩容部分源码(具体的扩容机制):
/* Enlarge the free space at the end of the sds string so that the caller
* is sure that after calling this function can overwrite up to addlen
* bytes after the end of the string, plus one more byte for nul term.
*
* Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have. */
/*
* 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
* buf 至少会有 addlen + 1 长度的空余空间
* (额外的 1 字节是为 \0 准备的)
*
* 返回值
* sds :扩展成功返回扩展后的 sds
* 扩展失败返回 NULL
*
* 复杂度
* T = O(N)
*/
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
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;
}
参考书籍《redis设计与实现》