redis---sds(简单动态字符串)详解

1,sds定义

    sds是simple dynamic string的缩写,意为简单动态字符串。

    定义为:

struct sdshdr {
    long len;         //记录buf数组已使用的字节数量,即sds保存的字符串的长度,不含'\0'
    long free;        //记录buf数组中未使用字节的数量
    char buf[0];      //字节数组,用于保存字符串
};

   如图:

redis---sds(简单动态字符串)详解

  len:4,表示这个数组保存的字符串的长度是4。

  free:4,表示这个数组的未使用空间是4。

  buf:是一个字符数组,前4个字节保存的是字符'L'、‘u’、‘o’、‘L’,最后一个字节保存的是字符串结束标识‘\0’。

  sds遵循C字符串以空字符结尾的惯例,保存空字符的1字节不计算在sds的len中,并且为空字符分配额外的1字节空间,以及添  加空字符到字符串末尾等操作,是由sds函数自动完成的。这样遵循空字符结尾这一惯例的好处是,sds可以直接重用一部分c字符串函数库里面的函数。

2,sds与c字符串的区别

     既然sds中保存的也是字符串,为什么不直接使用字符串,而还要封装成一个结构体呢?

     sds的三点优势:

  • 常数复杂度获取字符串的长度,通过len属性获取长度,复杂度是O(1),字符串循环判断,是O(n)。
  • 杜绝缓冲区溢出。
  • 减少修改字符串时带来的内存分配次数。

3,杜绝缓冲区溢出

     C字符串不记录自身长度带来的另一问题就是容易造成缓冲区溢出。而sds API需要对sds进行修改时,API会先检查sds的空间是否满足修改要求,如果不满足的话,API会自动将sds的空间扩展至需要的大小,然后执行下面的动作。

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

     由于C字符串不记录长度,所以对于一个包含N个字符的C字符串来说,这个字符串的底层实现总是一个N+1个字符长的数组,所以每次增长或缩短一个C字符串,都要对这个数组进行一次内存重分配。

    而sds中,buf数组的长度不一定是字符数量加一,数组里面可以包含未使用的字节,通过free 属性,实现了空间预分配和惰性空间释放两种优化策略。

   1) 空间预分配。

         空间预分配用于优化sds的字符串增长操作:当sds的API 对一个sds 进行修改,并且需要对sds进行空间扩展的时候,不仅会为sds 分配修改所必须的空间,还会为sds 分配额外的未使用空间。

       其中,额外分配的未使用空间数量由以下公式决定:

      A,如果对sds 进行修改后,sds的长度(len值)将小于1MB,那么分配和len 同样大小的未使用空间,这时sds 的len和free 的值相同。比如,进行修改后,sds 的len 将变成7字节,那么也会分配7字节的未使用空间,sds 的buf数组的实际长度将变成

7+ 7 + 1字节(额外的1字节保存空字符)。

redis---sds(简单动态字符串)详解

     B,如果对sds 进行修改后,sds的长度(len值)将大于等于1MB,那么分配1MB的未使用空间,即free的值是1MB。比如。如果修改后,sds 的len将变成30MB,那么会分配1MB 的未使用空间,sds 的buf数组的实际长度将变成30MB + 1MB + 1字节(额外的1字节保存空字符)。

    2) 惰性空间释放

          惰性空间释放用于优化sds 的字符串缩短操作:当sds的API 需要缩短sds 保存的字符串时,程序并不立即使用内存分配来回收缩短后多出来的字节,而是使用free 属性将这些字节数量记录起来,等待将来使用。

5,二进制安全

     A,C字符串中的字符必须符合某种编码(一般是ASCII编码),并且除了字符串的末尾外,字符串里面不能有空字符。

     B,sds 的API 都是二进制安全的,所有的sds API都会以处理二进制的方式来处理sds 存放在buf数组里的数据,不会对数据有限制。即redis 不是用这个数组来保存字符,而是用它来保存一系列二进制数据。因为sds 使用len属性的值来而不是空字符来判断字符串是否结束。

6,总结

redis---sds(简单动态字符串)详解