Redis数据结构-简单动态字符串

136 阅读3分钟

一、数据结构

struct sdshdr {
  // 数据空间
  char buf[];
  
  // buf中已使用的长度
  int len;
  
  // buf中剩余可用的长度
  int free;
};

二、SDS与C字符串的区别

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

  • c字符获取长度需要遍历字符串,直到发现空字符'\0',时间复杂度为O(n);
  • 而SDS对象存储了字符串的长度len,因此可以直接获取长度。

2. 杜绝缓冲区溢出

  • 当使用strcat(char* dest, const char* src)拼接c字符串时,我们假定dest分配的内存空间是足够的,如果内存空间不足,就会发生缓冲区溢出,意外的修改其他的内存空间的内容。
  • 而修改sds字符串时会先判断sds的空间是否满足要求,如果不满足要求会将sds的空间扩展至满足要求,然后再进行修改操作。

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

c字符串每次增加或减少字符时,都会进行内存重分配操作。因此,SDS字符串采用以下两种优化策略来针对字符串增长和缩短操作。

空间预分配

  • 如果修改sds字符串之后,字符串的长度小于1MB,那么将额外分配和len相同长度的未使用空间。如:修改之后如果字符串的长度为15字节,则总共分配的内存空间为:15 + 15 + 1(额外的一字节用于保存空字符'\0')
  • 如果修改sds字符串之后,字符串的长度大于等于1MB,那么将额外分配1MB的未使用空间。

通过空间预分配策略,可以减少连续对字符进行增长操作带来的内存重分配次数。

惰性释放内存空间

对sds字符串进行缩短操作时,并不会立即释放多余的内存空间,而是使用free记录未使用的内存空间,以备将来使用。

4. 二进制安全

  • c字符串只能保存文本数据
  • sds字符串不仅可以保存文本数据,还可以保存任意的二进制数据

三、SDS API

函数 作用 时间复杂度
sdsnew 创建一个sds,使用c字符串初始化 O(n) n为字符串长度
sdsempty 创建一个空的sds,不包含任何内容 O(1)
sdsfree 释放给定的sds O(n) n为sds长度
sdslen 返回已使用空间字节数 O(1)
sdsavail 返回未使用空间字节数 O(1)
sdsdup 创建一个给定sds的副本 O(n) n为sds的长度
sdsclear 清空sds保存的字符串内容 O(1)
sdscat 将给定的c字符串拼接到sds的末尾 O(n) n为c字符串的长度
sdscatsds 将给定的sds拼接到另一个sds的末尾 O(n) n为被拼接的sds的长度
sdscpy 将给定的c字符串复制到sds中,并覆盖原有的字符串 O(n)
sdsgrowzero 用空字符将sds扩展至指定的长度 O(n)
sdsrange 保留指定区间内的数据,不在区间内的数据将被清除 O(n)
sdstrim 接受一个sds和c字符串,从sds中移除在c字符串中出现过的字符 O(n^2)
sdscmp 比较两个sds O(n)