一、数据结构
struct sdshdr {
// 数据空间
char buf[];
// buf中已使用的长度
int len;
// buf中剩余可用的长度
int free;
};
二、SDS与C字符串的区别
1. 常数复杂度获取字符串的长度
- c字符获取长度需要遍历字符串,直到发现空字符
'\0',时间复杂度为O(n); - 而SDS对象存储了字符串的长度
len,因此可以直接获取长度。
2. 杜绝缓冲区溢出
- 当使用
strcat(char* dest, const char* src)拼接c字符串时,我们假定dest分配的内存空间是足够的,如果内存空间不足,就会发生缓冲区溢出,意外的修改其他的内存空间的内容。 - 而修改sds字符串时会先判断sds的空间是否满足要求,如果不满足要求会将sds的空间扩展至满足要求,然后再进行修改操作。
3. 减少了修改字符串带来的内存重分配次数
c字符串每次增加或减少字符时,都会进行内存重分配操作。因此,SDS字符串采用以下两种优化策略来针对字符串增长和缩短操作。
空间预分配
- 如果修改sds字符串之后,字符串的长度小于1MB,那么将额外分配和len相同长度的未使用空间。如:修改之后如果字符串的长度为15字节,则总共分配的内存空间为:15 + 15 + 1(额外的一字节用于保存空字符'\0')
- 如果修改sds字符串之后,字符串的长度大于等于1MB,那么将额外分配1MB的未使用空间。
通过空间预分配策略,可以减少连续对字符进行增长操作带来的内存重分配次数。
惰性释放内存空间
对sds字符串进行缩短操作时,并不会立即释放多余的内存空间,而是使用free记录未使用的内存空间,以备将来使用。
4. 二进制安全
- c字符串只能保存文本数据
- sds字符串不仅可以保存文本数据,还可以保存任意的二进制数据
三、SDS API
| 函数 | 作用 | 时间复杂度 |
|---|---|---|
| sdsnew | 创建一个sds,使用c字符串初始化 | O(n) n为字符串长度 |
| sdsempty | 创建一个空的sds,不包含任何内容 | O(1) |
| sdsfree | 释放给定的sds | O(n) n为sds长度 |
| sdslen | 返回已使用空间字节数 | O(1) |
| sdsavail | 返回未使用空间字节数 | O(1) |
| sdsdup | 创建一个给定sds的副本 | O(n) n为sds的长度 |
| sdsclear | 清空sds保存的字符串内容 | O(1) |
| sdscat | 将给定的c字符串拼接到sds的末尾 | O(n) n为c字符串的长度 |
| sdscatsds | 将给定的sds拼接到另一个sds的末尾 | O(n) n为被拼接的sds的长度 |
| sdscpy | 将给定的c字符串复制到sds中,并覆盖原有的字符串 | O(n) |
| sdsgrowzero | 用空字符将sds扩展至指定的长度 | O(n) |
| sdsrange | 保留指定区间内的数据,不在区间内的数据将被清除 | O(n) |
| sdstrim | 接受一个sds和c字符串,从sds中移除在c字符串中出现过的字符 | O(n^2) |
| sdscmp | 比较两个sds | O(n) |