字符串在 Redis 中很常用,键值对中的键是字符串类型,值有时也是字符串类型
Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char*
字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simple dynamic string,SDS)的数据结构来表示字符串
C 语言字符串的缺陷
C 语言的字符串就是一个字符数组,char *
指针指向字符数组的起始位置,字符数组的结尾位置用 '\0'
表示
- C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n)
- C 语言的字符串用
'\0'
表示结束,所以只能存放文本数据,不能存放二进制数据 - C 语言的字符串不会记录自身的缓冲区大小,诸如
strcat
等函数会假定程序员已经为dest
分配了足够多的内存,可以容纳src
字符串中的所有内容,而一旦这个假定不成立,就会发生缓冲区溢出
SDS 结构设计
SDS 结构体包含:
len
:字符串长度- 获取长度的复杂度为 O(1)
alloc
:分配给字符数组的空间长度- 在修改字符串时,不需要手动修改空间大小,也不会缓冲区溢出
- 因为修改字符串相关 API 会检查空间是否足够,空间不足会自动扩容,扩容规则:
- 如果所需的长度小于 1 MB,进行翻倍扩容,即
2 * newlen
- 否则,最后的扩容长度是
newlen + 1MB
- 如果所需的长度小于 1 MB,进行翻倍扩容,即
flags
:用来表示不同类型的 SDS- 一共设计了 5 种类型:
sdshdr5
、sdshdr8
、sdshdr16
、sdshdr32
、sdshdr64
- 用于节省内存空间
- 一共设计了 5 种类型:
buf[]
:字符数组,用来保存实际数据- 不仅可以保存字符串,也可以保存二进制数据
- SDS 为了兼容部分 C 语言标准库的函数,字符串结尾还是会加上
'\0'
字符
节省内存空间
不同类型 flags
的 SDS 其 len
和 alloc
成员变量的数据类型不同(保存小字符串时,结构头占用空间也比较少)
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len;
uint16_t alloc;
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len;
uint32_t alloc;
unsigned char flags;
char buf[];
};
在 struct 声明 __attribute__ ((packed))
,作用是:告诉编译器取消结构体在编译过程中的优化对齐,按照实际占用字节数进行对齐