Redis数据结构(一) — SDS

Redis—SDS简单动态字符串

Redis没有直接使用C语言传统的字符串表示(空字符结尾的字符数组), 而是自己构建了一种名为简单动态字符串的抽象类型, 并将SDS用作Redis的默认字符串表示

在Redis里面,C字符串只会作为字符串字面量, 用在一些无须对字符串值进行修改的地方,比如打印日志.

但当可能会修改字符串值时,就需要SDS来实现字符串, 比如,包含字符串值的键值对在底层就是由SDS实现的.

除此之外, SDS还被用作缓冲区 : AOF模块中的AOF缓冲区, 以及客户端状态中的缓冲区,都是由SDS实现的.

1.SDS的定义

struct sdshdr {

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

2.SDS与C字符串的区别

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

因为C字符串并不记录自身的长度信息,所以为了获取一个C字符串的长度,必须遍历整个字符串,对遇到的每个字符进行计数,直到遇到代表字符串结尾的空字符为止, 这个操作的复杂度为O(n)

而SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度为O(1)

设置和更新SDS长度的工作是由SDS的API在执行时自动完成的,使用SDS无须手动修改长度

2.杜绝缓冲区溢出

除了获取字符串长度的复杂度高, C字符串不记录自身长度的另一个问题是容易造成缓冲区溢出(buffer overflow), 比如说用<string.h>的strcat函数,可以将src字符串中的内容拼接到dest字符串的末尾

char *strcat(char *dest, char *src)

但如果在内存中, dest字符串后面紧紧连接着另外一个字符串s, 如果调用此函数,就会导致dest溢出到原来s的位置, 导致s字符串被意外修改

但SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性; 当SDS API需要对SDS进行修改时, API会先检查SDS的空间是否满足修改所需的要求, 如果不满足,API会自动将SDS的空间扩展至执行修改所需的大小, 然后才能执行实际的修改操作

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

C语言修改字符串时,比如拼接,或截取,会进行内存重分配操作,扩展或释放字符串的空间.如果修改字符串长度的情况不太常出现,那么每次修改都执行一次内存充分配是可以接受的.

但是Redis作为数据库, 经常被用于速度要求严苛, 数据被频繁修改的场合,如果每次修改字符串就会执行一次内存重分配的话, 会影响系统的效率和性能

为了避免这种缺陷,SDS通过未使用空间解除了字符串长度和底层数组长度的关联; 在SDS中, buf数组的长度不一定就是字符数量加一, 数组里面可以包含未使用的字节, 而这些字节的数量就由SDS的free属性记录

通过未使用空间,SDS实现了空间预分配惰性空间释放两种优化策略

  • 1.空间预分配

    当SDS的API对一个SDS进行修改, 并且需要对SDS进行空间扩展的时候, 程序不仅会为SDS分配修改所必须要的空间, 还会为SDS分配额外的未使用空间, 由一下的策略决定 :

    • 1.当SDS进行修改后, SDS的长度(len)小于1MB, 那么程序分配和len属性同样大小的未使用空间
    • 2.当SDS修改后长度大于1MB, 那么程序会分配1MB的未使用空间

在扩展SDS空间之前, SDS API会先检查未使用空间是否足够, 如果足够的话, API就会直接使用未使用空间, 而无须执行内存重分配

  • 2.惰性空间释放

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

    通过惰性空间释放策略, SDS避免了缩短字符串时所需的内存重分配操作, 并为将来可能有的增长操作提供了优化.

4.二进制安全

C字符串中的字符必须符合ASCII码, 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾, 这些限制使得C字符串只能保存文本数据, 而不能保存像图片, 音频, 视频, 压缩文件这样的二进制数据

为了确保Redis可以适用于各种不同的使用场景, SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据.

这也就是将SDS的buf称为字节数组的原因—Redis不是用这个数组来保存字符, 而是用来保存一系列二进制数据; 例如: SDS使用len属性的值而不是空字符来判断字符串是否结束

虽然SDS的API都是二进制安全的, 但他们一样遵循C字符串以空字符结尾的管理, 这些API总会将SDS保存的数据的末尾设置为空字符, 并且总会为bubf数组分配空间的时候多分配一个字节来容纳这个空字符,这是为了让那些保存文本数据的SDS可以重用一部分<string.h>库定义的函数

猜你喜欢

转载自blog.csdn.net/wintershii/article/details/89429925