1.何为SDS
SDS(simple dynamic string)又名简单动态字符串,是Redis的基本数据结构,Redis中包含字符串的键值对的底层均是SDS实现。众所周知,Redis使用C语言编写的,至于Redis为何不用C语言中的字符串表示,且听我娓娓道来。
2.定义
下面我们来看看SDS的结构:
struct sdshdr{
// 记录了SDS保存的字符串的长度,换句话说就是buf数组中使用的字节数
int len;
// buf数组中未使用字节数
int free;
// 用来保存字符串的字节数组
char bur[];
};
需要注意的是SDS中的buf数组虽然以空字符'\0'结尾,但是空字符的1字节空间是不会计算在SDS的len属性中,SDS在分配空间时会额外分多1字节空间给空字符。由于SDS同C字符串一样遵循空字符结尾,所以可以直接对SDS使用C语言的函数进行操作。
3.与C字符串的区别
下面我们将对比SDS与C字符串的区别,解释Redis选择SDS而非C字符串的原因。
3.1获取字符串长度仅需常数复杂度
由于C字符串中无记录自身长度信息的属性,所以每次获取C字符串的长度都需要程序从头开始遍历整个字符串直到遇到空字符,所以C字符串获取自身长度的复杂度为O(N)。
反观SDS,Redis想获取字符串长度只需访问SDS的len属性即可,其复杂度为O(1)。
3.2杜绝缓冲区溢出
因C字符串不记录自身长度,从而容易引发缓冲区溢出的问题——当给字符串分配的内存不足时,字符串的数据就会溢出到其他数据所处的空间之中,造成数据被修改。
SDS的API在执行对SDS的修改时,会先检查SDS的空间是否满足修改的要求,不满足的话API会自动将SDS的空间进行扩充至符合修改要求。
3.3减少修改字符串时带来的内存重分配次数
也因为C字符串不记录自身长度的缘故,每次缩短或扩展一个字符串时,程序会对C字符串的数组进行内存重分配操作,主要分为append(拼接)和trim(截取),append时未扩充底层数组空间则会造成缓冲区溢出,trim时忘记释放底层数组空间则会造成内存泄露。
为了不踩C字符串的坑,SDS实现了空间预分配和惰性空间释放两种策略:
-
空间预分配
空间预分配用于解决SDS扩展的问题:对SDS进行扩展时,API不仅分配SDS修改所需空间,而且为SDS分配额外的未使用空间。修改后空间小于1MB时,修改后的len属性将与free属性相同;大于等于1MB时,将额外分配多1MB额外空间,下图是将图1的Ross字符串修改为Jim Ross的情况:
-
惰性空间释放
惰性空间释放用于解决SDS缩短的问题:API在缩短SDS时,不是直接将内存重分配来回收缩短空间,而是将多余空间用free属性记录起来给以后使用,下图是将图2中的Jim Ross修改为Jim的情况:
3.4 二进制安全
举一个例子,假如我们有一个由多个空字符隔开的字符串比如一个英文句子,如果我们使用C字符串存储则只会识别到第一个空字符就停止了。若我们使用SDS,API则会以处理二进制的方式处理SDS中buf数组的数据,程序不对其中的数据做任何限制。SDS是以len属性为标准判断一个字符串是否结束而不是空字符。
3.5兼容部分C字符串函数
前面我们已经提到了我们可以直接对SDS使用C语言的函数进行操作,具体是C语言中的<string.h>中的部分函数比如strcasecmp、strcat等。
后记
由于本人能力有限,如有不当或错误的地方,欢迎大家批评指正。
微信搜——Ross的记录空间
我是Ross,我们下次再见。
参考
《Redis设计与实现》