Redis没有使用C语言传统的字符串表示,而是构建了一种简单动态字符串(SDS)的类型,C字符串只会作为字符串字面量在一些无需对字符串值进行修改的地方,比如打印日志。

SDS用来保存数据库中的字符串值、用作缓冲区:AOF缓冲区,客户端状态缓冲区。

SDS定义:

struct sdshdr {    int len;//记录buf数组中已使用字节的数量,等于SDS所保存字符串的长度    int free;//记录buf数组中未使用字节的数量    char buf[];//字节数组,用于保存字符串}

SDS遵循C字符串以空字符结尾的惯例,保留空字符的1字节空间,不计算在len属性里面,并且为空字符分配额外的1字节空间。

SDS与C字符串的区别

  1. 常数复杂度获取字符串长度
  2. 杜绝缓冲区溢出
  3. 减少修改字符串时带来的内存重分配次数
  4. 二进制安全
  5. 兼容部分C字符串函数

常数复杂度获取字符串长度

C语言:遍历整个字符串
SDS:读取len属性

杜绝缓冲区溢出

C语言:不记录自身长度,没有判断空间是否满足修改后的大小
SDS:api会先检查SDS的空间是否满足修改所需的需求

减少修改字符串时带来的内存重分配次数

C语言拼接操作:先通过内存分配扩展底层数组空间大小
C语言截断操作:截断后释放不再使用的那部分空间
SDS:空间预分配和惰性空间释放

空间预分配

如果对SDS修改后,len小于1MB,将会分配len属性同样的大小的未使用空间,free = len,buf的长度为len + free + 1byte = 2 * len + 1byte,如果len大于等于1MB,将会分配1MB的未使用空间,free = 1MB,buf的长度为len+free+1byte=len+1MB+1byte

惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作,当缩短字符串时,程序并不立即使用内存重新分配来回收缩短后多出来的字节,而是使用free记录下来,避免了缩短字符串所需的的内存重新分配操作,并且为将来可能有得增长操作提供了优化。同时,SDS也提供了相应的API,在我们有需要时,真正的释放SDS的未使用空间,不用担心内存浪费。

二进制安全

C语言:字符必须符合某种编码,并且除了字符串末尾,不能包含空字符,所以C字符串只能保存文本数据
SDS:使用buf来存字节数组,len来判断字符串长度,字符串是否结束,所以可以保存文本数据和二进制数据

兼容部分C字符串函数

SDS的buf因为以空字符结尾,所以可以兼容部分C字符串函数

C字符串 SDS
获取字符串长度的复杂度为O(N) 获取字符串长度的复杂度为O(1)
API是不安全的,可能会造成缓冲区溢出 API是安全的,不会造成缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配 修改字符串长度N次最多需要执行N次内存重分配
只能保存文本数据 可以保存文本或者二进制数据
可以使用所有<string.h>库中的函数 可以使用一部分<string.h>库中的函数

C字符串和SDS之间的区别