sds定义

sds.h/sdshdr 结构定义了sds的结构:

struct sdshdr {
//记录buf数组使用字节数量,即字符串长度
int len;

//记录buf数组未使用字节数量
int free;

//字节数组,保存字符串
char buf[];

len: 字符串长度

和c字符串区别

c语言不记录字符串长度,获取字符串长度需要遍历一遍字符串,时间复杂度O(N);sds用len属性记录字符串长度,所以时间复杂度为O(1)。

杜绝缓冲区溢出

c语言字符串拼接,strcat(),如果没有实现分配空间会造成内存溢出,造成该内存存的内容被修改。

而sds的空间分配策略则杜绝了缓冲区溢出的问题。当修改sds时,会对检查sds空间是否满足修改所需的要求,如果不满足会自动扩展sds空间至所需空间大小。

减少修改字符串时带来的内存重分配次数

c语言对于字符串的增长与缩短操作,都要进行一次内存重分配操作:
增长:比如拼接字符串strcat,在执行这个操作之前,需要先通过内存重分配扩展底层数组的空间大小,如果忘了这一步会产生缓冲区溢出。
缩短:比如截断操作trim函数,在执行这个操作之前,需要先通过内存重分配释放那段不使用的空间,如果忘了这一步会产生内存泄漏。

为了避免C字符串这种缺陷,sds通过未使用空间解除了字符串长度和底层数组长度之间的关联:
在sds中,buf数组的长度不一定就是字符串个数加1,数组里可以包含未使用的字节,而这些字节的数量就由sds的free属性记录。
通过未使用空间,sds实现了空间预分配惰性空间释放两种优化策略。
1.空间预分配
空间预分配用于优化sds字符串增长操作:
当需要扩充空间时,不仅会分配修改必须要的空间,还会分配额外的未使用空间。
其中额外分配未使用空间数量由公式决定:

  • 对sds修改后,如果长度小于1mb,那么分配和len同样大小的未使用空间,这是len属性和free属性相同。举个例子,修改后len值变成10字节,那么free也等于10,buf数组实际长度变为10+10+1=21字节,1字节用于保存空字符。
  • 对sds修改后,如果长度大于等于1mb,那么程序会分配1mb的额外未使用空间,即额外分配的未使用空间上限为1mb。
    通过空间预分配策略,redis可以减少连续执行字符串增长操作所需的内存分配次数。

2.惰性空间释放
惰性空间释放用于优化sds字符串增长操作:
当字符串需要缩短操作时,sds并没有立即使用内存重分配回收缩短后多出来的字节,而是使用free属性将这些字节数量记录起来,等待下次使用。
sds也提供了对应的api,在有需要时,真正释放sds的未使用空间,所以不需担心内存浪费。

二进制安全

c语言字符串必须符合某种编码(如ASCII),并且以'0'结尾,所以限制必须存文本数据,而不能存图片、音频、视频、压缩文件等二进制数据。
为了确保redis可以满足多场景,sds的api会以二进制的方式来处理存在buf数组里的数据,不会对其限制、过滤,怎么存就怎么读。
所以buf数组也是字节数组,不是用来保存字符,而是保存二进制数据。

总结sds对比c字符串的优势

  1. 常数复杂度获取字符串长度
  2. 杜绝缓冲区溢出
  3. 减少修改字符串时内存重分配次数
  4. 二进制安全

sds有五种。分别为sds5 、sds8 、sds16 、sds32 、sds64 其中sds5从未使用过、之所以会有五种类型,就是为了省内存,我们看下其中四种的结构的定义。以sds8为例

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

标签: redis

添加新评论