Redis-简单动态字符串(SDS)

219 阅读4分钟

1.何为SDS

  SDS(simple dynamic string)又名简单动态字符串,是Redis的基本数据结构,Redis中包含字符串的键值对的底层均是SDS实现。众所周知,Redis使用C语言编写的,至于Redis为何不用C语言中的字符串表示,且听我娓娓道来。

2.定义

下面我们来看看SDS的结构:

struct sdshdr{
  // 记录了SDS保存的字符串的长度,换句话说就是buf数组中使用的字节数
  int len;
  // buf数组中未使用字节数
  int free;
  // 用来保存字符串的字节数组
  char bur[];
};

图1-SDS结构图示

  需要注意的是SDS中的buf数组虽然以空字符'\0'结尾,但是空字符的1字节空间是不会计算在SDS的len属性中,SDS在分配空间时会额外分多1字节空间给空字符。由于SDS同C字符串一样遵循空字符结尾,所以可以直接对SDS使用C语言的函数进行操作。

3.与C字符串的区别

  下面我们将对比SDS与C字符串的区别,解释Redis选择SDS而非C字符串的原因。

3.1获取字符串长度仅需常数复杂度

  由于C字符串中无记录自身长度信息的属性,所以每次获取C字符串的长度都需要程序从头开始遍历整个字符串直到遇到空字符,所以C字符串获取自身长度的复杂度为O(N)

  反观SDS,Redis想获取字符串长度只需访问SDS的len属性即可,其复杂度为O(1)

3.2杜绝缓冲区溢出

  因C字符串不记录自身长度,从而容易引发缓冲区溢出的问题——当给字符串分配的内存不足时,字符串的数据就会溢出到其他数据所处的空间之中,造成数据被修改。

  SDS的API在执行对SDS的修改时,会先检查SDS的空间是否满足修改的要求,不满足的话API会自动将SDS的空间进行扩充至符合修改要求。

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

  也因为C字符串不记录自身长度的缘故,每次缩短或扩展一个字符串时,程序会对C字符串的数组进行内存重分配操作,主要分为append(拼接)和trim(截取),append时未扩充底层数组空间则会造成缓冲区溢出,trim时忘记释放底层数组空间则会造成内存泄露

  为了不踩C字符串的坑,SDS实现了空间预分配和惰性空间释放两种策略:

  • 空间预分配

    空间预分配用于解决SDS扩展的问题:对SDS进行扩展时,API不仅分配SDS修改所需空间,而且为SDS分配额外的未使用空间。修改后空间小于1MB时,修改后的len属性将与free属性相同;大于等于1MB时,将额外分配多1MB额外空间,下图是将图1的Ross字符串修改为Jim Ross的情况:

    图2-Ross字符串修改为Jim Ross

  • 惰性空间释放

    惰性空间释放用于解决SDS缩短的问题:API在缩短SDS时,不是直接将内存重分配来回收缩短空间,而是将多余空间用free属性记录起来给以后使用,下图是将图2中的Jim Ross修改为Jim的情况:

    图3-Jim Ross修改为Jim

3.4 二进制安全

  举一个例子,假如我们有一个由多个空字符隔开的字符串比如一个英文句子,如果我们使用C字符串存储则只会识别到第一个空字符就停止了。若我们使用SDS,API则会以处理二进制的方式处理SDS中buf数组的数据,程序不对其中的数据做任何限制。SDS是以len属性为标准判断一个字符串是否结束而不是空字符。

3.5兼容部分C字符串函数

  前面我们已经提到了我们可以直接对SDS使用C语言的函数进行操作,具体是C语言中的<string.h>中的部分函数比如strcasecmp、strcat等。

后记

  由于本人能力有限,如有不当或错误的地方,欢迎大家批评指正。

  微信搜——Ross的记录空间

  我是Ross,我们下次再见。

参考

《Redis设计与实现》