在Redis中没有直接使用C语言传统的字符串,而是自己构建了简单动态字符串SDS,那么为什么Redis需要自己构建字符串呢?
首先Redis用作缓存数据库,对于存储速度要求是非常苛刻的,传统的C字符串对于字符串的操作比如strcat每次都需要重新划分空间,涉及到系统调用以及内存分配算法等会占用修改字符串的大部分时间,这当然不是我们想看到的,再比如我们计算传统的C字符串长度我们要调用strlen函数去获取,这里的时间复杂度就是O(N),而是用动态字符串SDS的时间复杂度就是O(1),下面就来揭晓原因:
SDS的定义在sds.h中
struct sdshdr{
int len;//记录了字符串的长度
int free;//字符串未使用的字节数
char buf[];//保存字符串
};
结构体中第一个len就记录了字符串的长度,第二个free字段是为了在操作字符串的过程中减少重新分配内存的次数,这里涉及到两个概念:空间预分配和惰性空间释放
空间预分配:
比如我们对一个字符串“Redis”(len=5,free=0,buf={'R','e','d','i', 's','\0'}),进行sdscat(“Redis”, "Mysql")操作后它就会变成(len=10,free=10,buf={'R','e','d','i', 's','M','y',,'s','q','l','\0'})
,这样有什么好处呢?
首先我们知道使用strcat(dest, src)函数已经默认为dest分配了足够的内存空间,一旦使用不当会影响到相邻的内存空间,排查和定位问题的难度也非常大,而且每一次都需要重新分配和释放内存,对于Redis这种缓存数据库肯定是接收不了的,而使用sdscat就完美的解决了这些问题,sdscat在执行拼接操作之前会检查dest的长度是否足够,如果不足会扩展其空间,然后进行拼接操作,而且由于free属性,使得后续进行拼接的时候分配内存的次数从O(N)降低为最多O(N)。
此外我们正常使用的C字符串都是以'\0'结束,因此它只能用来存放文本数据,而对于Redis来说还需要存放图片、音视频、压缩文件等,显然是不合适的,因此对于SDS来说是二进制安全的,但它一样遵循C字符串以'\0'结尾的惯例,因此对于文本数据而言可以使用C库下的字符串函数来对SDS字符串进行操作。保证了一定的兼容性。
总结:
C字符串 | SDS |
获取字符串长度的复杂度为O(N) | 获取字符串长度的复杂度为O(1) |
API是不安全的,可能造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 |
修改字符串长度N次时间复杂度O(N) | 修改字符串长度N次时间复杂度最多O(N) |
只能保存文本数据 | 可以保存文本或二进制数据 |
可以使用所有<string.h>库中的函数 | 可以使用部分<string.h>库中的函数 |