Redis(一)简单动态字符串
什么是SDS简单动态字符串
Redis只有在不需要修改字符串的值还是直接展示字符串值的地方使用C语言字符串(以空字符结尾的字符数组),比如打印日志。在会修改字符串值的地方都是使用SDS简单动态字符串。比如包含字符串值的键值对都是使用的SDS来底层实现的。
除了用来保存数据库中的字符串值以外,SDS还被用做缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的。
SDS的定义
每个sds.h/sdshdr结果表示一个SDS值:
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于SDS所保存的字符串长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串值
char buf[];
}
SDS遵循C字符串以空字符结尾的惯例。遵循这一惯例的好处是,SDS可以直接使用一部分C字符串函数库里面的函数。
SDS与C字符串的区别
-
获取字符串长度的区别
C字符串没有保存字符串的长度,想要获取字符串长度需要从保存字符的buf数组的头部一个个遍历到尾部去计算长度,而SDS的结构里有一个len字段保存了SDS字符串的长度,在插入、更新、删除字符串时,会自动更新维护这个字段。 -
SDS可以杜绝缓冲区溢出
SDS的结构里存在一个free字段,这个字段代表的是buf数组里未被分配的空间。而C字符串里是没有未被分配的空间的,所以当C字符串在拼接另外一个字符串的时候,如果没有判断目前C字符串是否分配了足够的空间,当空间不足的时候就会导致缓冲区溢出。
比如:存在两个字符串A和B,这两个字符串在内存是相连的,当字符串A的内容被修改了,这个时候如果没有先去判断字符串A的长度是否能够容纳新值,同时这个新值也真的超过了字符串A的空间长度,这个时候字符串A修改完后也会将字符串B的值也修改了。 -
SDS可以减伤修改字符串带来的内存重新分配次数
C字符串因为并不记录自身的长度,所以每次增长或缩短C字符串,都需要对C字符串的数组进行一次内存重新分配:- 如果程序执行的是增长字符串的操作,程序需要先通过内存重新分配来扩展底层的数组大小,不进行这个操作就会导致缓冲区溢出。
- 如果程序执行的是缩短字符串的操作,程序需要先通过内存重新分配来释放掉底层数组未使用的空间,不进行这个操作就会导致内存泄漏。
SDS保留了一个free字段,记录buf数组里未被分配的空间。当进行要增长字符串长度的时候,如果未分配空间足够大,那么就可以直接增长字符串长度。当进行要缩短字符串长度的时候,可以直接缩短字符串长度,缩短后留下的空间可以直接划给未分配空间,留待下次增长字符串长度时使用。
- 当SDS字符串长度未达到1M时,free的大小和SDS字符串长度的大小一致。
- 当SDS字符串长度大于1M时,free的大小一直为1M。
-
SDS的buf数组保存的是二进制数据
C字符串因为需要以空字符串来标识一个字符串的结尾,所以导致C字符串只能保存文本数据,不能保存像图片、视频等二进制数据。但是SDS的结构len字段保存了SDS字符串的长度,不再需要以空字符串来标识字符串的结尾,所以SDS字符串的buf数组保存的是二进制数据,这让Redis能够缓存更多种类的数据。