简单动态字符串SDS

简单动态字符串

Redis使用简单动态字符串(simple dynamic string,SDS)来表示字符串;需要修改的字符串,例如:key value等;

Redis使用C字符串作为字面量;不需要修改的字符串;

C字符串:C语言传统的字符串,以空字符结尾的字符数组,以下简称C字符串;

SDS的定义

struct sdshdr{     
    //记录buf数组中已使用字节的数量     
    //等于SDS所保存字符串的长度    
    int len;     
    //记录buf数组中未使用字节的数量     
    int free;     
    //字节数组,用于保存字符串     
    char buf[]; 
}

存储结构:

简单动态字符串SDS_第1张图片

free属性的值为0,表示这个SDS没有分配任何未使用空间。

len属性的值为5,表示这个SDS保存了一个五字节长的字符串。

buf属性是一个char类型的数组,数组的前五个字节分别保存了'R'、'e'、'd'、'i'、's'五个字符,而最后一个字节则保存了空字符'\0'。

注意:SDS遵循C字符串的惯例以空字符结尾,保存空字符的1字节不计算在SDS的len属性中;好处:可以重用C字符串的一些函数;

 

SDS与C字符串的区别:

比起C字符串,SDS具有以下优点:

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

  • C字符串不记录自身长度,获取C字符串长度时必须遍历整个字符串计数得到,复杂度是O(N);
  • SDS字符串自身记录维护len长度属性,获得SDS字符串长度的复杂度是O(1);

2)杜绝缓冲区溢出。

  • C字符串不记录长度,由于两个C字符串在内存存储上紧邻,在执行字符串拼接strcat时,如果不提前分配足够空间,很可能发生修改s1的数据溢出到s2所在的空间中;(缓冲区溢出)
  • SDS完全杜绝了缓冲区溢出问题,它记录了长度,当修改SDS字符串之前,API都会检查SDS的空间是否满足修改的要求,不满足API会自动进行空间扩展。

3)减少修改字符串长度时所需的内存重分配次数。

  • C字符串每次修改增加或减少时,都会对保存这个C字符串的数组进行一次内存重分配;
    • 拼接操作,在执行之前,程序需要先通过内存重分配来扩展底层数组的空间大小——如果忘了这一步就会产生缓冲区溢出。
    • 截断操作,在执行之后,程序需要通过内存重分配来释放字符串不再使用的那部分空间——如果忘了这一步就会产生内存泄漏。
    • 内存重分配算法复杂还需要执行系统调用,如果发生频繁修改可能对性能造成影响。
  • SDS通过free未使用空间,实现了空间预分配和惰性空间释放两种优化策略。
    • 空间预分配
      • 空间预分配:增长操作时,Redis可以减少连续执行字符串增长操作所需的内存重分配次数
      • 当SDS做增长操作,不仅会分配修改所必须要的空间,还会额外分配未使用空间,具体分配未使用空间如下2种方式:
        • 一:如修改后长度len小于1MB,就分配和len属性相同大小的未使用空间;free=len;
        • 二:如修改后长度len大于等于1MB,就分配1MD的未使用空间;free=1MB;
    • 惰性空间释放
      • 惰性空间释放,缩短操作时:SDS避免了缩短字符串时所需的内存重分配操作,并为将来可能有的增长操作提供了优化
      • 当SDS做缩短操作,不会立刻使用内存重分配来收回缩短后多出来的字节,而是保持在free属性里,等待将来使用。
      • 同时SDS也提供了API手动进行释放SDS未使用的空间,不必担心惰性释放策略会造成内存浪费。

4)二进制安全。

  • C字符串的字符必须符合某种编码,除结尾空字符以外,字符串内部不允许有空字符串。存储有局限性;
  • SDS存储的都是二进制安全的字符串,SDS API都会以处理二进制的方式来处理SDS存在buf数组里的数据。(这也是SDS的buf称之为字节数组的原因因——Redis不是用这个数组来保存字符,而是用它来保存一系列二进制数据)
  • SDS不是用空字符来表示结尾,是以len长度标识结尾。

5)兼容部分C字符串函数。

  • 虽然SDS是二进制安全的,但是还秉承C字符串以空字符结尾的特性。很多函数是可以共用C字符串的不需要重写;

 

简单动态字符串SDS_第2张图片

 

黄健宏 著. Redis设计与实现 (Chinese Edition) (Kindle位置561). Kindle 版本.

 

你可能感兴趣的:(Redis,redis)