SDS
Redis没有直接使用C字符串,而是自己构建了一种简单的动态字符串SDS,并作为Redis的默认字符串。
在Redis中,C字符串只会作为字符串的字面量用在一些无需对字符串修改的地方如打印日志 ,而对于可以被修改的字符串会使用SDS ,
如包含字符串的键值对底层都是用SDS实现的。
例:
redis> SET msg "hello world"
OK
除了用来保存数据库中的字符串值,还被用作缓冲区:AOF模块中的AOF缓冲区,客户端状态中的输入缓冲区。
SDS详解:
SDS定义在源码的 sds.h中的 sdshdr中:
struct sdshdr{
//sds中保存的字符串的长度及buf已使用的字节数
int len;
//sds未使用的字节数
int free;
//字节数组
char buf[];
};
图:
SDS字符串也以空字符串结尾,保存空字符串的一个字节不计算在SDS的len属性中,
为空字符串分配1个字节并且把它加到字符串末尾的操作由sds函数自动完成。
SDS与C字符串的区别:
C使用N+1个字节来表示长度为N的字符串,并且最后一个元素是空字符’\0’。
C字符串无法记录自身的长度,所以想获得长度必须遍历整个字符串。复杂度O(N)。SDS记录了自身的长度,获取长度的复杂度O(1)。
C字符串无法记录自身的长度,在做字符串拼接时,容易导致缓冲区溢出。而SDS的API需要对SDS进行修改时会先检查SDS的空间是否满足需求,
不满足则会自动将SDS的空间扩大,既不要手动修改SDS的大小也不会导致缓冲区溢出。C字符串无法记录自身的长度,包含长度为N的字符串总是使用N+1个字节来实现,每次增长或缩短都需要对内存重新分配,
内存重分配可能需要执行系统调用,对性能造成影响。SDS通过未使用空间解除了字符串长度和底层数组长度的关联。而且未使用空间使用了
空间预分配和惰性空间释放两种优化策略。C字符串必须符合某种编码,并且除了字符串末尾外其他地方不可以出现空字符,否则就出现错误,所以C字符串只能保存文本数据,不能是图片、
音乐等二进制数据。而SDS的API会以处理二进制数据的方式来处理buf中的数据,所以SDS的buf被称为字节数组。SDS遵循C字符串以空字符结尾,所以可以重用C的一部分
优化策略:
空间预分配
用于优化SDS字符串增长的操作,当SDS的API对空间做扩展的时候,不仅会为SDS分配必须的空间,还会额外多分配一些。额外分配的空间大小公式
如下:- 若对SDS修改后,SDS大小小于1M,那么会分配长度和len一样的未使用空间,所以len值和free值一样。
- 若修改后大小大于1M,那么会分配1M
这样在扩展SDS空间之前,SDS API会先检查未使用空间大小是否足够,若足够就直接使用,不够就执行内存重分配。
惰性空间释放
- 用于优化SDS字符串缩短操作,当SDS的API对空间缩短字符串,不会立即对内存重分配来回收缩短后多出来的字节,而是用free属性来将这些
字节记录起来。
通过惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配的操作,并为将来的可能有的增长提供优化。
- 用于优化SDS字符串缩短操作,当SDS的API对空间缩短字符串,不会立即对内存重分配来回收缩短后多出来的字节,而是用free属性来将这些
SDS主要操作API
函数 | 作用 | 时间复杂度 |
---|---|---|
sdsnew | 创建一个包含C字符串的SDS | O(N),N为字符串的长度 |
sdsempty | 创建一个不包含任何内容的空SDS | O(1) |
sdsfree | 释放给定的SDS | O(N),N为释放的SDS的长度 |
sdslen | 返回SDS已使用的字节数 | O(1) |
sdsavail | 返回SDS未使用的字节数 | 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里,覆盖SDS原有的字符串 | O(N),N为被复制C字符串的长度 |
sdsgrowzero | 用空字符串将SDS扩展到给定长度 | O(N),N为新增的字节数 |
sdsrange | 保留SDS给定区间内的数据,不在区间内的数据会被覆盖或清除 | O(N),N为被保留的字节数 |
sdsrim | 接受一个SDS和一个C字符串作为参数,从SDS中移出C字符串出现过的字符 | O(N),N为给定的C字符串的长度 |
sdscmp | 对比两个SDS字符串是否相同 | O(N),N为SDS中较短的那个的长度 |