1、Redis字符串的特点
- 已知Redis的键值对的键是字符串对象,值也可以是字符串对象,值还可以是列表对象、哈希对象、集合对象、有序集合对象总共这五种对象。
- Redis的底层实现语言是经典的C语言,Redis的字符串定义与C语言的字符串定义有一些区别
– 1、C语言的字符串对象是以空字符结尾的字符类型数组,有多少个字符该数组就是多大的
– 2、Redis使用的是简单动态字符串(SDS, simple dynamic string)的抽象类型,SDS除了用来保存Redis的字符串对象之外,还可以用作缓冲区。
SDS的数据结构定义如下:
strcut sdshdr{
int len;//用来保存数组中已使用字节的数量=SDS所保存的字符串的长度
int free;//用来保存数组中未使用字节的数量
int buf[];//字节数组,用来保存字符串。大小=len + free
}
SDS的结构用图像表示如下,以保存字符串"Redis"的一个SDS为例:
- 从中可以看出,free=0,表示该SDS的未使用空间大小为0;
- len=5,表示SDS的保存的字符串的大小为5字节;
- buf的属性是一个char类型的数组,其中保存了如上所示的字符,0个未使用字符空间,还要’结尾处的/0‘空字符。
- 如果要获取该SDS保存的字符串的长度,不用遍历字符串,直接使用len的值就行。获取长度方便,就能有效防止缓冲区溢出的风险,还能减少修改字符串(如拼接等操作)带来的内存重分配资源消耗
2、SDS如何实现减少修改字符串带来的内存重分配
结论:SDS使用空间预分配和惰性空间释放两种机制来避免标题所示问题
内存重分配:字符串拼接可能带来缓冲区溢出,字符串缩短可以造成内存泄露,这就需要对内存空间进行重分配,这种操作涉及到的算法复杂,可以需要执行系统级别的调用,所需要的资源和时间比较多,要尽量避免,可以使用空间换时间的策略。
(1)空间预分配
当对SDS的API对SDS进行修改,需要对SDS进行空间扩展的时候,会带来两个空间的扩展:所必须的字符串空间,额外的未使用空间
- len的长度<1MB时,分配的未使用空间大小=len
- len的长度>=1MB时,分配的未使用空间大小=1MB
- 也就是len<1MB的时候free=len,大于的时候free=1MB
通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需要的内存重分配次数。
//示例:
sdscat(s,"Tutorial");//将s和"Tutorial"字符串拼接
对标题1的SDS执行以上程序时,因为未使用字符串空间不够,将执行一次内存重分配操作:len修改为拼接之后的大小并填入数据,free变为13字节。
如果再次执行上述拼接代码,free空间够用,将不会产生内存重分配。
(2)惰性空间释放
惰性空间释放针对优化SDS的字符串缩短操作,当SDS的API需要缩短SDS所保存的字符串的时候,并不一定要立刻进行内存重分配来释放空闲出来的字节
- 将释放出来的字节转换为空闲字节,同时更新free属性的大小
- 此操作能避免缩短字符带来的内存重分配操作,还为可能的增长操作带来了缓存的空间
参考资料:<Redis设计与实现>第一章