简单动态字符串
简单动态字符串(simple dynamic string,SDS)
SDS的结构
struct sdshdr {
// 记录buf数组中已使用字节的数量
// 等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
'\0':空字符
SDS遵循C字符串以空字符结尾的惯例,
保存空字符的1字节空间不计算在len
好处:SDS可以直接重用一部分C字符串函数库里面的函数。
使用场景
字符串字面量:c字符串
可以被修改的字符串:SDS来表示字符串值
SDS的好处
C语言使用长度N+1的字符数组来表示长度为N的字符串,
并且字符数组的最后一个元素总是空字符'\0'
不能满足Redis对字符串在安全性、效率以及功能方面的要求。
1)常数复杂度获取字符串长度
SDS在len属性中记录了SDS本身的长度
设置和更新SDS长度的工作是由SDS的API在执行时自动完成的。
2)杜绝缓冲区溢出
当SDS API需要对SDS进行修改时,
1)API会先检查SDS的空间是否满足修改所需的要求,
2)如果不满足,API会自动将SDS的空间扩展至执行修改所需要的大小
3)然后才执行实际的修改操作
3)减少修改字符串时带来的内存重分配次数
SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联:
在SDS中buf数组的长度不一定就是字符数加一,
数组里面可以包含未使用的字符,
而这些字节的数量就由SDS的free属性记录
”空间预分配“和”惰性释放“
空间预分配
优化了SDS的字符串增长操作:
对SDS进行空间扩展的时候,
不仅会为SDS分配修改所必须要的空间,
还会分配额外的未使用空间(记录在free里)。
额外分配的未使用空间数量:
len < 1MB
程序分配和len属性同样大小的未使用的空间
len >= 1MB
程序分配1MB的未使用空间
好处:可以减少内存重分配次数
惰性空间释放
优化了SDS的字符串缩短操作
当需要缩短SDS保存的字符串时,
不会立即使用内存重分配来回收缩短后多出来的字节,
而是使用free属性将这些字节的数量记录起来,并等待将来使用。
好处:避免了缩短字符串时所需的内存重分配操作,
并对将来可能有的增长操作提供了优化。
SDS也提供了相应的API(sdsclear),在需要的时候,真正地释放SDS的未使用空间。
二进制安全:
SDS的API都是二进制安全的,都会以处理二进制的方式来处理SDS存放在buf数组里的数据
数据在写入时是什么样的,被读出时就是什么样
buf属性称为字节数组的原因:Redis不是用这个数组来保存字符,而是用它来保存一系列二进制数据。
SDS使用len属性值而不是空字符来判断字符串是否结束。
C字符串和SDS之间的区别
--------------------------------------+--------------------------------------------
C字符串 | SDS
--------------------------------------+--------------------------------------------
获取字符串长度的复杂度O(N) | 获取字符串长度的复杂度O(1)
--------------------------------------+--------------------------------------------
API是不安全的, 可能会造成缓冲区溢出 | API是安全的, 不会造成缓冲区溢出
--------------------------------------+--------------------------------------------
修改字符串长度N次必然执行N次内存重分配 | 修改字符串长度N次最多需要执行N次内存重分配
--------------------------------------+--------------------------------------------
只能保存文本数据 | 可以保存文本数据或者二进制数据
--------------------------------------+--------------------------------------------
可以使用所有<string.h>库中的函数 | 可以使用部分<string.h>库中的函数
--------------------------------------+--------------------------------------------
参考 《Redis设计与实现》