C语言中传统的字符串:以空字符结尾的字符数组,不可修改
Redis创建了一个新的简单动态字符串SDS,并作为Redis默认字符串表示,应用于一些字符串要求被修改的场景!
使用场景:
在Redis中,C字符串只作为字符串字面量,用在不需要改变字符串的场景,比如打印日志
当Redis不只是需要字符串字面量,而是可以被修改的字符串值,就可以SDS来表示字符串值;比如Redis的数据库里面,包含字符串的键值对在底层都是由SDS实现的
例子①:
redis >SET msg "hello world"
OK
Redis将在数据库中,创建一个新的键值对
- 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串“msg”的SDS
- 键值对的值也是一个字符串对象,对象的底层实现是一个保存着字符串"hello world"的SDS
例子②:
redis >RPUSH fruits "apple","banana","cherry"
(integer) 3
Redis将在数据库中,创建一个新的键值对
- 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串“fruits”的SDS
- 键值对的值也是一个列表对象,列表对象包含三个字符串对象,这三个字符串对象分别由三个SDS实现,第一个SDS保存着字符串"apple",第二个SDS保存着字符串"banana",第三个SDS保存着字符串"cherry"
SDS的定义
每一个sds.h/sdshdr结构表示一个SDS值:
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用的字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
};
free属性:记录SDS中未使用的空间
len属性:记录SDS中已经使用的空间
buf属性:是一个char类型的数组,最后一个字节保存空字符’\0’
SDS特点
字符串和C中的字符串一样,都是以空字符结尾,并且空字符占用的1个字节空间不计算在len属性中,分配给空字符1个字节空间,以及自动添加空字符到字符串尾部都是SDS自动完成的
遵循空字符结尾的好处是可以使用一部分C字符串函数库中的函数,不需要再为SDS编写专门的一些函数。
SDS和C字符串的区别/也可以理解成已经有了C字符串,还要去设计SDS的原因
C语言中的字符串
长度为N+1的字符数组表示长度为N的字符串,最后一个元素为空字符’\0’
1.获取字符串长度的复杂度为O(1),提高了Redis的性能
C的复杂度为O(N)
因为要逐个元素进行遍历
因为SDS的属性len就记录了SDS的长度,直接获取len属性就可以知道字符串的长度,所以复杂度为O(1)
2.杜绝缓冲区溢出
为什么SDS可以避免上面这种情况?
当SDS的API对SDS进行修改的时候,API会先检查SDS的空间是否满足修改所需要的要求,比如内存等
如果检查发现不满足要求,API会自动将SDS的空间进行扩展至所需要的大小,然后再去执行实际的操作
3.减少修改字符串时带来的内存重分配次数
C语言
因为不记录字符串的长度,每次修改其长度的时候就和底层数组发生改变
,进行内存的重分配
如果增长字符串,先通过内存重分配扩展底层数组的空间大小
如果缩短字符串,先通过内存重分配释放字符串不需要的那部分内存
SDS
SDS通过未使用空间解除字符串长度和底层数组长度之间的关联
在SDS中,buf数组的长度不一定就是字符串数量加1,数值里面包含着未使用的字节,而这些字节的数量是通过SDS的free属性记录的
对于未使用空间,SDS采用了两种优化策略
空间预分配
用于字符串增长操作,当需要字符串增长时候,API对SDS进行修改并且对空间进行扩展,不仅会分配修改所需要的必要的空间,还会分配额外未使用的空间,也就是增长两个空间:要使用的空间和未使用的空间
惰性空间释放
用于字符串缩短操作,并不会立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性讲这些字节记录下来,并等待将来使用
总结
SDS API