简单动态字符串
redis的字符串表示不是使用的像C语言那种以空字符结尾的字符数组,而是自己构建了一种简单动态字符串(simple dynamic string)——SDS。
而一般来说,redis只会在字符串字面量被用在一些无需对字符串进行更改的地方使用C风格的字符串,比如打印日志。
SDS的定义
sds定义在sds.h的sdshdr的结构体中:
1 | struct sdshds { |
之所以在SDS中,使用了与C字符串类似的风格,是因为这样可以直接使用C字符串函数库里面的函数,比如要打印字符串,直接使用:
1 | printf("%s", s->buf); |
SDS与C字符串的区别
要了解两者之间的区别,首先要从结构体入手。
常数复杂度获取字符串的长度
这个比较好理解,因为结构体本身带有长度域,而设置和更新SDS长度的工作又是由SDS的API执行时自动完成的,所以避免了影响redis的性能。
对于C字符串来说,要获取字符串长度,只能逐个遍历,直到遇到了空字符。
杜绝缓冲区溢出
这个也比较好理解。对于一些字符串拼接的操作,如果没有记录字符串长度,无法得知字符串剩余的内存,这样在拼接的时候就可能发生缓冲区溢出。
而SDS的API在使用sdscat函数进行字符串拼接的时候,会先检查SDS的空间是否足够,不足的话,会使用某种分配策略扩展空间。
减少修改字符串带来内存重分配次数
对于C字符串来说,每次增长或者缩短一个字符串,程序都需要对保存该字符串的数组进行一次重新分配内存的工作,一旦没有重新分配,就可能造成内存泄露或者产生缓冲区溢出。而且因为内存重分配涉及到了复杂的算法,并且有可能会执行系统调用,所以通常是比较耗时的操作。
为了避免C字符串的这种缺陷,redis的SDS实现了空间预分配和惰性空间释放两种优化策略。
- 空间预分配
这个策略主要用于优化SDS的字符串增长操作,在进行空间扩展时,不仅仅会分配所需空间,还会为SDS分配额外的未使用空间;
- 如果对SDS修改后,长度小于1MB,那么程序将分配和len属性同样大小的未使用空间。即如果SDS的len变成了13字节,那么整个buf的数组长度将会变成13+13+1=27个字节;
- 如果对SDS修改后,长度大于1MB,那么程序将会分配1MB的未使用空间;
- 惰性空间释放
该策略会使得SDS在缩短长度时,不会马上使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节数记录下来,并等待将来使用
二进制安全
相比C字符串在读到空字符,则认为是字符串结尾,redis的SDS则是用len属性来检验字符串是否结束。这种二进制安全的做法使得redis不仅可以保存文本格式,还可以保存任意格式的二进制数据