Redis数据结构-简单动态字符串(SDS)
1. 简介
Redis没有使用C中默认的字符串(字符数组),而是自己构建了一个简单动态字符串(SDS)的抽象类型,并且作为默认的字符串。
- Redis中会使用C中的字符串作为字符常量,用在无需修改的地方,如日志;
- 当字符串会被修改时,就是使用SDS;
- 此外,SDS还会被用作缓冲区。
2. 定义
sds.h/sdshdr
结构体表示一个SDS值:
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};
相比普通的C字符串,SDS的优点在于:
- 能够通过len字段方便的知道字符串长度;
- 通过free字段能方便知道缓存区剩余空间;
- 同样字符串buf以'\0'结尾,能够与print等C中函数接口通用;
- 杜绝缓冲区溢出(SDS会先进行检查,空间不够会进行扩展)。
- 二进制安全。
在使用SDS API的情况下,不要手动修改len和free字段,它们的维护是API内部自动进行的。
3. 内存优化策略
3.1 空间预分配
在使用SDS API进行字符串修改需要扩容时,不仅会分配修改所必须的空间,而且还会分配额外的未使用空间。总体策略:
- 如果修改后的SDS长度(len字段)小于1MB,那么程序会分配同len相同的未使用空间,即free=len,实际长度是free+len+1=2len+1;
- 如果修改后的SDS长度(len字段)大于1MB,那么程序会分配1MB的未使用空间。
通过空间预分配,可以减少内存分配次数。
3.2 惰性空间释放
释放策略是:弹药使用API缩短SDS字符串时,程序并不会回收多出来的字节,而是使用free字段记录下来,以等待将来被使用。
通过惰性空间释放,可以减少内存回收的次数,未回收的内存可以继续用于字符串的存储。
如果要回收内存,SDS提供了相应的API。
4. 特点
4.1 二进制安全
虽然SDS的buf保存了C的\0
结尾的约定,但是它实际内容其实是由len字段决定,因此存储音视频等二进制数据是没有问题的,不用对数进行任何过滤操作。
SDS也因此被称为字节数组--保存二进制数据。
4.2 兼容部分C函数
SDS仍然遵循C字符串以空白字符结尾的惯例,因此它也能使用C字符串中<string.h>部分函数。
如stacat、strcmp等。
区别:
- C字符串只能保存文本数据,而SDS可以二进制数据;
- C字符串API操作可能会导致缓冲区溢出,而SDS不会;
- SDS只能使用部分C函数;
- SDS获取字符串长度的时间复杂是O(1),而普通C字符串是O(n);
- 空间扩展,SDS会进行预分配,实际分配次数会比C字符串少。