SDS简单动态字符串
Redis没有直接使用C语言中的字符串,而是自己构建了一个抽象类型,简单动态字符串SDS(Simple Dynamic Strings)。为何没有使用C语言的字符串,来看下两者的区别
C字符串与SDS的区别
C字符串 | SDS |
---|---|
获取字符串长度复杂度为O(n) | 获取字符串长度复杂度为O(1) |
会出现缓冲区溢出 | 杜绝缓冲区溢出 |
只能保存文本数据 | 可保存文本和二进制数据 |
修改字符串长度N次必然需要执行N次内存重新分配 | 修改字符串长度N次最多需要执行N次内存重新分配 |
SDS用途
- 存储键值对的键和值
- 列表对象中的字符串
- 用作缓冲区(buffer): AOF 模块中的 AOF 缓冲区, 以及客户端状态中的输入缓冲区
SDS数据结构实现
struck sdshdr{
// 占用空间:用于记录buf数组中使用的字节的数目,和SDS存储的字符串的长度相等
unsigned int len;
// 剩余空间:用于记录buf数组中没有使用的字节的数目
unsigned int free;
//数据空间:用来存储字符串,buf的大小等于len+free+1,其中多余的1个字节是用来存储’\0’的。
char buf[];
}
SDS的优点
- 可以直接读取len和free属性来快速获取数据长度和剩余空间,时间复杂度为O(1)
- 内容存储在动态数组 buf 中,SDS 对上层暴露的指针指向 buf,而不是指向结构体 SDS。因此,上层可 以像读取 C 字符串一样读取 SDS 的内容,兼容 C 语言处理字符串的各种函数,同时也能通过 buf 地址 的偏移,方便地获取其他变量;
- 读写字符串使用len属性,不像C语言中依赖于 \0,保证二进制安全。
- 杜绝缓冲区溢出
- 减少修改字符串时带来的内存重新分配次数
什么机制来减少内存重新分配
在C语言中,每次对字符串进行增长或缩短时,程序都会进行一次内存重新分配。例如:
- 字符串拼接操作(append),程序先为增长后的字符串分配内存空间,如果忘记了这一步操作就会产生缓冲区溢出
- 截断操作(trim)程序需要通过内存重新分配来释放字符串中不再使用的空间,如果忘记了这一步操作就会产生内存泄漏
为了避免C字符串的这种缺陷,Redis中的SDS通过以下两种策略来优化
空间预分配
用于优化SDS字符串增长操作,扩展前先检查free空间是否够用,足够的话无需分配,不足在进行分配,同时free也会获得同样的len空间
惰性空间释放
用于优化SDS字符串缩短操作,缩短字符串时不会重新分配内存,而是更改free和len和buf,在需要的时候才会真正的释放free多余空间
通过这两种策略实现了修改字符串长度N次必然需要执行N次内存重新分配变成了最多
参考:
《Redis设计与实现》