Redis作为数据库,用于速度要求严苛,数据被频繁修改的场合。
Redis底层重新定义了字符串,称为Simple Dynamic String,简称SDS。
在Redis里面,C字符串只会作为字符串字面量,用在一些无需对字符串值进行修改的地方,比如打印日志:
redisLog(REDIS_WARMING,"Redis is now ready to exit,bye bye...");
SDS定义
在Redis数据库里面,包含字符串值的键值对在底层都是有SDS实现的。
struct sdshdr {
//用于保存字符串的长度,等于buf数组中已使用的字节的数量
int
len;
//用于保存buf数组中未使用的字节的数量
int
free;
//字节数组,用于保存字符串
char
buf[];
}
free属性的值是3,表示这个SDS还有3个字节的空间可以分配。
len属性的值是4,表示这个SDS保存了一个4字节长的字符串。
buf属性是一个char类型的数组,前四个字符分别保存了'j','a','v','a'四个字符,最后一个字符则保存了空字符'\0',保存空字符的1字节空间不计算在SDS的len属性中。为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符对于使用者来时都是透明的。
SDS与C字符串的区别
①.获取字符串长度。
C字符串并不记录自身的长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,将遇到的每个字符进行计数;而对SDS来说,程序只需要访问SDS的len属性就可以知道字符串的长度。
②杜绝缓存区溢出
假设程序里有两个在内存种紧邻着的C字符串s1('R' 'e' 'd' 'i' 's' '\0')和s2('M' 'o' 'n' 'g' 'o' 'D' 'b' ' \0'),如果对s1的内容修改为"Redis Cluster",但是没有给s1充分分配足够的空间,那么s1的数据将溢出到s2中,s2的内容将被意外的修改。
SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足,API会自动对SDS空间进行扩容,然后才执行修改操作。
③减少修改字符串时带来的内存重分配次数
C字符串如果是增加字符串的操作,程序需要通过内存重新分配来扩展底层内存数组的空间大小,否则会产生内存溢出。
C字符串如果是缩短字符串的操作,程序需要通过内存重新分配来释放底层内存数组的不再使用的那部分空间,否则会产生内存泄露。
SDS通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略。
空间预分配策略:
当SDS的API对一个SDS进行修改,并且对SDS空间进行拓展的时候,程序不仅会给SDS分配所必须要的空间,还会未SDS分配额外未使用的空间。
在拓展SDS空间之前,SDS API会检查未使用空间是否足够,如果足够的花,直接使用未使用空间,不需要重新分配内容。
惰性空间释放:
当SDS 的API 需要缩短保存的字符串时,程序并不会立即进行内存重新分配,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
二进制安全
C字符串种的字符必须符合某种编码(如ASCII),并且除了字符串的末尾之外,字符串里不能包含空字符。这些限制使得C字符串只能保存文本数据,而不能保存图片,音频,压缩文件等这样的二进制数据。
SDS的API都是二进制安全的,所有的SDS API都会以处理二进制的方式处理SDS存在在buf里的数据,而且SDS也是以空字符结尾,所以可以兼容C字符串的不放呢函数。