Redis学习(2)——SDS简单动态字符串

1、什么简单动态字符串?

​ Redis 底层是用C语言编写的,可是在字符存储上,并未使用C原生的String类型,而是定义了自己的字符串结构 Simple Dynamic Stirng,简称SDS。

SDS基本结构如下:

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

例如,字符串“Redis”存储示意图为

当前存储字符串长度为5,未使用长度为0,字节数组存储的字符为“Redis\0”。这里需要注意的是:内部数据数组存储字符串形式符合C语言要求,以‘\0’结尾。且len字符串长度不包含结尾标识符‘\0’。buf[]的这种遵循C语言形式的存储,使得Redis可以直接使用C语言的相关字符串函数进行SDS对象的操作。

2、SDS的特点?

2.1、O(1)时间复杂度获取字符串长度

SDS内部维护着一个字符串长度的len变量,可以直接读取,时间复杂度为O(1)。

对于传统的C字符串:字符+“\0”,想要获取字符长度,则需要遍历整个字符串,直到遇到结束字符,时间复杂度为O(n)。

2.2、缓冲区溢出规避

所谓缓冲区溢出即所需要的内存超出了实际的内存。因此对于C字符串来说,要特别注意内存分配,回收使用问题。

比如,向一个现有字符串内添加特定字符时,需要保证当前已经分配了这足够的内存。

与C不同的是,SDS的空间预分配策略可以避免缓冲区溢出发生,

当需要对SDS进行操作时,首先会检查当前空间是否满足需求,不足则扩展当前分配空间。内存检查相对于C变成了内部预置操作。

2.3、减少内存重分配次数

上面我们讲到过,C字符操作前都需要进行内存的分配操作,同时,操作完成后,也需要进行相应的内存回收操作。一次操作至少涉及一次内存分配操作。

大家都知道内存的重分配是一个比较复杂且需精细控制的过程,耗时耗资源。针对此弊端,Redis 在SDS内存配置策略上采用了空间预分配+惰性删除相结合的策略。

a)空间预分配:

空间预分配用于优化SDS字符扩展操作。

所谓预分配,也即是说在一次扩展操作中,扩展的空间大小会大于实际需要的空间大小
如下,图1执行图2操作后SDS变更为:

预分配空间的大小基于以下规则计算:

  • 如果修改后len长度将小于1M,这时分配给free的大小和len一样,例如修改过后为10字节, 那么给free也是10字节,buf实际长度变成了10+10+1 = 21byte
  • 如果修改后len长度将大于等于1M,这时分配给free的长度为1M,例如修改过后为30M,那么给free是1M.buf实际长度变成了30M+1M+1byte

这样,在下次进行字符操作的时候,如果所需要的空间小于当前SDS free空间,则可以直接行操作,而不需要再执行内存扩展,重分配操作。

SDS的预分配机制使得一次扩展操作所需的内存重分配次数变为<=1。

b)惰性删除机制

所谓惰性删除,即调整删除SDS中部分数据时,不会立刻执行内存重分配,而是会保留空出来内存,并更新内部free属性。以备将来有字符扩展需求,可以直接使用。

当然,Redis也提供了主动释放未使用内存的方法。

如下,删除“ent”之后的SDS结构:

SDS的内存分配机制,尤其对于以写为主的应用场景,能够提供更加优异的性能表现。

2.4、二进制安全

​ C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。如果有一种使用空字符来分割多个单词的特殊数据格式,就不能用C字符串来表示,如"Redis\0String",C字符串的函数会把'\0'当做结束符来处理,而忽略到后面的"String"。而SDS的buf字节数组不是在保存字符,而是一系列二进制数组,SDS API都会以二进制的方式来处理buf数组里的数据,使用len属性的值而不是空字符来判断字符串是否结束。

3、SDS的API?

4、参考资料

https://www.cnblogs.com/niejunlei/p/12896605.html

https://www.cnblogs.com/hunternet/p/9957913.html

你可能感兴趣的:(Redis学习(2)——SDS简单动态字符串)