1、Redis设计SDS的背景

优化C语言char* 字符数组的不足

  • 操作效率低:获取长度需遍历,O(N)复杂度

  • 二进制不安全:无法存储包含 \0 的数据(char* 数组用’\0’ 表述字符串结束 )

2、SDS数据结构

SDS 结构中记录了字符数组 使用的长度和分配的空间大小,避免了对字符串的遍历操作,可以直接获取到字符串的长度len,降低了操作开销 (空间换时间)

struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 字符数组现有长度(uint8_t 是8位无符号整形,占用1字节)*/
uint8_t alloc; /* 字符数组的已分配空间,不包括结构体和0结束字符*/
unsigned char flags; /* SDS类型*/
char buf[]; /*字符数组*/
};
  1. flags表示SDS的类型,一共有5种类型 sdshdr5(已废弃)、sdshdr8、sdshdr16、sdshdr32、sdshdr64, 区别:不同类型SDS结构中len和alloc这俩个元数据的数据类型不同(类型sdshdr8 表示 字符数组长度(包括最后一位0)不超过256字节(2^8)

SDS设计不同的结构头(即不同类型),是为了能灵活保存不同大小的字符串,从而有效节省内存空间(因为保存不同大小的字符串时,结构头占用的内存空间也不一样,保存较小的字符串时,结构头也就占用较小的空间)

  1. attribute ((packed))的作用: 告诉编译器,在编译sdshdr8结构时,不要使用字节对齐的方式,而是**采用紧凑的方式分配内存-按实际占用的字节数,**来实现紧凑型内存布局,达到节省内存的目的

​ (字节对齐的方式: 这是因为在默认情况下,编译器会按照8字节对齐的方式,给变量分配内存。也就是说,即使一个变量的大小不到8个字节,编译器也会给它分配8个字节**)**

3、SDS的优势

  • 操作效率高:获取长度无需遍历,O(1)复杂度

  • 二进制安全:因单独记录长度字段,所以可存储包含 \0 的数据

  • 兼容 C 字符串函数,可直接使用字符串 API

  • 采用紧凑的方式分配内存

  • 内存预分配:SDS 扩容,会多申请一些内存(小于 1MB 翻倍扩容,大于 1MB 按 1MB 扩容)

  • 避免频繁的内存分配:SDS 缩容,不释放多余的内存,下次使用可直接复用这些内存 (这种策略,是以多占一些内存的方式,换取「追加」操作的速度,内存预分配策略,详细逻辑可以看 sds.c的 sdsMakeRoomFor 函数)