Redis ——SDS
Redis并没有使用C语言的字符串表示,而是自己构建了“简单动态字符串”(simple dynamic string,SDS)的抽象类型,而且Redis将SDS作为自己的默认字符串表示。
一、SDS的定义
SDS使用sds.h/sdshdr的结构来表示一个SDS值:
struct sdshdr{
//记录buf数组中已使用字节的数量
//记录SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
};
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的1字节空间。采取这种手段的好处是,SDS可以直接重用C字符串函数库里面的函数。
二、SDS与字符串的区别
2.1 常数复杂度获取字符串长度
C字符串并不记录自身的长度,因此每次要获取一个C字符串的长度,都要遍历整个字符串。而SDS里面有一个len属性,可以直接获取SDS的长度。
2.2 杜绝缓冲区溢出
C字符串由于不记录自身长度,容易造成缓冲区溢出(“buffer overflow“)。因为strcat会假定用户在执行整函数的时候已经为字符串分配了足够多的内存,而一旦这个假定不成立,就会产生缓冲区溢出。
与C字符串不同,SDS的空间的分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足,API会自动将SDS的空间扩展至执行所需要的大小,然后才会执行实际的修改操作。
2.3 减少修改字符串时带来的内存分配次数
①空间预分配
空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会对SDS分配额外的未使用空间。
分配未使用空间的公式如下:
- 如果对SDS修改之后,SDS的长度(即len的值)将小于1MB,那么程序将会分配和len属性同样大小的未使用空间,这时SDS 的len属性的值将和free的属性相同。
- 如果对SDS修改之后,SDS的长度大于2MB,那么程序会分配1MB的未使用空间。
②惰性空间释放
惰性空间释放用于优化SDS的字符缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
2.4 二进制安全
为了确保Redis可以适用于处理各种不同的使用场景,SDS的API都是二进制安全(binary-safe),所有SDS API都会以处理二进制的方式来处理存放在buf数组里的数据。因此数据在写入时是什么样子,被读取的时候就是什么样子。
2.5 兼容部分C字符串函数
由于SDS遵循C字符串以空字符结尾的惯例,所以它仍然可以使用C字符串的方法。