《Redis设计与实现》学习笔记

第2章 简单动态字符串(SDS)

  redis的字符串不是直接用c语言的字符串,而是用了一种称为简单动态字符串(SDS)的抽象类型,并将其作为默认字符串。

2.1 SDS定义

 1 /*
 2  * 保存字符串对象的结构
 3  */
 4 struct sdshdr {
 5 
 6     // buf 中已占用空间的长度
 7     int len;
 8 
 9     // buf 中剩余可用空间的长度
10     int free;
11 
12     // 数据空间
13     char buf[];
14 };

  SDS遵循C字符串以空字符结尾的惯例,但是那1个字节不计算在len中。

  可以重用C字符串库函数里的函数。

2.2 SDS与C语言字符串的区别

1、常数复杂度获取字符串长度

  C语言如果要获取字符串的长度,需要从第一个字符开始,遍历整个字符串,直到遍历到\0符号,时间复杂度是O(N),即字符串的长度。

  而redis由于已经存储了字符串的长度,因此,时间复杂度是O(1)。

  这样,避免了获取大字符串长度时时间的缓慢。

2、杜绝缓冲区溢出

  C语言给字符串开辟一个存储空间,如果对此存储空间的使用超过开辟的空间,会导致内存溢出。

  例如使用字符串拼接等方式时,就很容易出现此问题。而如果每次拼接之前都要计算每个字符串的长度,时间上又要耗费很久。

  redis的SDS中内置一个sdscat函数,也是用于字符串的拼接。但是在执行操作之前,其会先检查空间是否足够

  如果free的值不够,会再申请内存空间,避免溢出。

3、减少内存分配次数

  C语言的字符串长度和底层数组之间存在关联,因此字符串长度增加时需要再分配存储空间,避免溢出;字符串长度减少时,需要释放存储空间,避免内存泄漏。

  redis的sds,主要是通过free字段,来进行判断。通过未使用空间大小,实现了空间预分配和惰性空间释放

1)空间预分配

  当需要增长字符串时,sds不仅会分配足够的空间用于增长,还会预分配未使用空间。

  分配的规则是,如果增长字符串后,新的字符串比1MB小,则额外申请字符串当前所占空间的大小作为free值;如果增长后,字符串长度超过1MB,则额外申请1MB大小。

  上述机制,避免了redis字符串增长情况下频繁申请空间的情况。每次字符串增长之前,sds会先检查空间是否足够,如果足够则直接使用预分配的空间,否则按照上述机制申请使用空间。

 1 /*
 2  * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
 3  * buf 至少会有 addlen + 1 长度的空余空间
 4  * (额外的 1 字节是为 \0 准备的)
 5  *
 6  * 返回值
 7  *  sds :扩展成功返回扩展后的 sds
 8  *        扩展失败返回 NULL
 9  *
10  * 复杂度
11  *  T = O(N)
12  */
13 sds sdsMakeRoomFor(sds s, size_t addlen) {
14 
15     struct sdshdr *sh, *newsh;
16 
17     // 获取 s 目前的空余空间长度
18     size_t free = sdsavail(s);
19 
20     size_t len, newlen;
21 
22     // s 目前的空余空间已经足够,无须再进行扩展,直接返回
23     if (free >= addlen) return s;
24 
25     // 获取 s 目前已占用空间的长度
26     len = sdslen(s);
27     sh = (void*) (s-(sizeof(struct sdshdr)));
28 
29     // s 最少需要的长度
30     newlen = (len+addlen);
31 
32     // 根据新长度,为 s 分配新空间所需的大小
33     if (newlen < SDS_MAX_PREALLOC)
34         // 如果新长度小于 SDS_MAX_PREALLOC 默认1M
35         // 那么为它分配两倍于所需长度的空间
36         newlen *= 2;
37     else
38         // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
39         newlen += SDS_MAX_PREALLOC;
40     // T = O(N)
41     newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
42 
43     // 内存不足,分配失败,返回
44     if (newsh == NULL) return NULL;
45 
46     // 更新 sds 的空余长度
47     newsh->free = newlen - len;
48 
49     // 返回 sds
50     return newsh->buf;
51 }

2)懒惰空间释放

  懒惰空间释放用于优化sds字符串缩短的操作

  当需要缩短sds的长度时,并不立即释放空间,而是使用free来保存剩余可用长度,并等待将来使用。

  当有剩余空间,而有有增长字符串操作时,则又会调用空间预分配机制。

  当redis内存空间不足时,会自动释放sds中未使用的空间,因此也不需要担心内存泄漏问题。

4、二进制安全

  SDS 的 API 都是二进制安全的: 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据, 程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的, 它被读取时就是什么样。

  sds考虑字符串长度,是通过len属性,而不是通过\0来判断。

5、兼容部分C语言字符串函数

  redis兼容c语言对于字符串末尾采用\0进行处理,这样使得其可以复用部分c语言字符串函数的代码,实现代码的精简性。

猜你喜欢

转载自www.cnblogs.com/mengchunchen/p/9025139.html