SDS(simple synamic String)用作Redis默认字符串表示。C字符串只会作为字符串字面量用在一些无须对字符串进行修改的地方,例如打印日志等。
SDS定义
每个sds.h/sdshdr结构表示一个SDS值
struct sdshdr {
//字符串的长度
int len;
// buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
SDS遵循C字符串以空字符结尾的惯例,保存空字符的一字节空间不计算再SDS的len属性,并且为空字符分配额外的1字节,添加空字符到字符串末尾等操作是由SDS函数自动完成的。好处是,可以直接重用一部分C字符串函数库里的函数。
SDS 与 C字符串的区别
获取字符串长度的复杂度
C字符串并不记录自身的长度信息,获去一个C字符串的长度,程序必须进行遍历整个字符串,对遇到的每个字符进行技术,直到遇到代表字符串结尾的空字符为止,这个操作的复杂度为O(N)
而SDS在len的属性中记录了SDS本身的长度,所以获去一个SDS长度的复杂度仅为O(1)。
通过使用SDS而不是C字符串,Redis将获去字符串长度所需的复杂度O(N)降低到O(1),确保了获去字符串长度不会成为Redis的性能瓶颈。
杜绝缓冲区溢出
C字符串如果未分配足够的空间情况下
而SDS的空间分配策略杜绝了发生缓冲区溢出的可能性。当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改的需求,如果不满足的话,会自动将SDS空间扩展至所需的大小,然后才执行实际修改的操作,所以SDS不需要手动修改SDS的空间大小,也不会出现缓冲区溢出
减少修改字符串时带来的内存重分配次数
C字符串每次要增加或者缩短一个C字符串,总要对这个C字符串的数组进行一个内存重分配操作:
- 如果程序执行的增长字符串的操作,执行操作之前,需要通过内存重分配来扩展底层数组的空间大小,如果忘了这一步就会产生缓冲区溢出。
- 如果程序执行的是缩短字符串的操作,执行操作之后,需要通过内存重新分配来释放字符串不再使用的那部分空间,如果忘了这一步就会产生内存泄漏。
注:内存重分配涉及复杂的算法,并且可能需要执行系统调用,是一个比较耗时的操作。
SDS通过未使用空间,实现了空间预分配和惰性空间释放两种优化策略
空间预分配
空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS修改,并且需要进行空间扩展的时候,程序不仅会为SDS分配修改所需要的空间,还会为SDS分配额外未使用空间。
额外分配未使用空间的分配:
- 修改之后,SDS的长度(len属性的值)小于1MB,那么分配和len属性同样大小的未使用空间。
- 修改之后,SDS的长度大于等于1MB,程序会分配1MB未使用空间
惰性空间释放
惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串,程序不会立即使用内存重分配来回收多出来的字节,而是使用free属性将这些字节的数量记录起来,并等将来使用。惰性空间释放策略不仅避免了缩短字符串时所需的内存重分配操作,还为将来可能的增长操作提供了优化。
二进制安全
SDS API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放的buf数组里的额数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,它被读取时就是什么样的
兼容部分C字符串的函数
总结:
redis只会使用C字符串作为字面量,在大多数情况下,redis使用SDS作为字符串的表示。
相比C字符串,SDS优点:
- 常数复杂度获去字符串长度
- 杜绝缓冲区溢出
- 减少修改字符串长度时所需的内存重分配次数
- 二进制安全
- 兼容部分C字符串函数