Redis简单动态字符串
Redis没有直接使用C语言传统的字符串表示,而是自己构建了一种名为SDS的抽象类型,并将其作为Redis的默认字符串表示。
在Redis中C字符串只会用在一些无须对字符串值进行修改的地方,比如打印日志
SDS的定义
每一个sds.h/sdshdr结构代表一个SDS值
struct sdshdr{
//记录buf数组中已经使用的字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用的字节数量
int free;
//字节数组,用于保存字符串
char buf[];
}
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节不计算到len属性中,这样做的好处是可以调用一部分C字符串函数库的函数。
SDS与C字符串的区别
1.常数复杂度获取字符串长度
C语言中需要对每一个字符进行遍历,直到遇到代表字符串结尾的字符为止,复杂度未O(N).
SDS直接使用其结构体中的len属性,len的大小就是字符串的长度.
2.杜绝缓存区溢出
因为C字符串不记录自身的长度所以strcat假定执行函数时已经有足够大的空间,而一旦这个假定不成立也就是没有足够大的空间就会造成缓存区溢出。
eg:假设内存中有两个相连的字符串S1和S2
当对S1进行连接操作时,如果没有提前分配足够大的空间,就会将S2中的内容覆盖。
然而在SDS中则完全杜绝了此类问题的发生,当SDSAPI需要对SDS进行修改时,API首先会检查空间是否满足需求,如果不满足就会自动扩展空间。
3.相比于C语言减少了修改字符串时带来的内存重分配次数
C语言字符串在修改过程中,对于一个包含了N个字符的C字符串来说,底层实现总时一个N+1长度的数组,所以每一次进行字符串的修改都会导致内存的重分配。
避免C字符串的性能缺陷:
SDS实现了空间与分配和惰性空间释放两种方案
空间预分配
1.修改SDS后,SDS的长度(len)将小于1MB,那么给free属性一个和len属性一样大小的未使用空间
2.同理修改SDS后,长度(len)大于1MB,那么将分配给free属性1MB的未使用空间。
惰性空间的释放
当SDS需要进行缩短操作时不会立即进行内存重分配,而是会将多出后的字节记录到SDS中的free属性中,方便下次的使用。与此同时SDS也存在相应的函数可以使SDS的空间真正的被释放,所以不用担心会造成内存浪费。
二进制安全
C语言字符串必须符合某种编码比如ASCII,字符串中不能包含’\0’。
为了确保Redis可以适用于任何不同的场景SDS的API都是二进制安全的,所有SDSAPI会被当做处理二进制的方式来进行处理SDS存放在buf数组里的数据。
buf被称为字符数组的原因:Redis不是用这个数组来保存字符,而是用它保存一些二进制数据。
eg:
字符串redis\0cluster\0
在C字符串中读取到Redis就会结束,而在SDS中使用len属性的值来判断字符串的结束,而不是\0标识符。