redis---字符串SDS(简单动态字符串)底层结构
文章目录
- 什么是SDS(简单动态字符串)
- SDS结构
- SDS的优点
- O(1) 时间复杂度获取字符串长度
- 避免缓冲区溢出
- 减少内存重分配次数
- 二进制安全
- 兼容C语言字符串函数
- SDS的操作
- 总结
什么是SDS(简单动态字符串)
redis是由C语言编写的,但是redis在实现字符串中并没有采用传统C语言中的字符串表示(传统的C语言字符串是一个以空字符‘\0’
结尾的字符数组char*),而是自己定义了一种叫做简单动态字符串(simple dynamic string, 简称SDS)的抽象类型,并用SDS用作redis默认的字符串表示。
SDS结构
Redis 5.0 及之后的版本
struct __attribute__ ((__packed__)) sdshdr {
uint8_t len; // 字符串的实际长度
uint8_t alloc; // 分配的总空间(不包括头部和结尾的 \0)
unsigned char flags; // 标志位,用于标识 SDS 的类型
char buf[]; // 存储字符串内容的柔性数组
};
-
len
:记录字符串的实际长度(不包括结尾的\0
)。 -
alloc
:记录分配的总空间(不包括头部和结尾的\0
)。 -
flags
:标志位,用于标识 SDS 的类型(如SDS_TYPE_8
、SDS_TYPE_16
等)。 -
buf
:存储字符串内容的动态数组,末尾会自动添加\0
,以兼容 C 语言的字符串函数。
新版SDS根据字符串的长度动态选择不同的类型:
-
SDS_TYPE_5
:长度小于 32。 -
SDS_TYPE_8
:长度小于 256。 -
SDS_TYPE_16
:长度小于 65536。 -
SDS_TYPE_32
:长度小于 2^32。 -
SDS_TYPE_64
:长度小于 2^64。
这种设计可以根据字符串的实际长度动态调整头部的大小,进一步节省内存。
SDS的优点
O(1) 时间复杂度获取字符串长度
C 语言的原生字符串需要通过遍历(strlen
)来获取长度,时间复杂度为 O(n)。
SDS 直接通过 len
字段获取长度,时间复杂度为 O(1)。
避免缓冲区溢出
C 语言的原生字符串操作(如 strcat
)容易导致缓冲区溢出。
// 将 src 字符串拼接到 dest 字符串后面
char *strcat(char *dest, const char* src);
C 语言的字符串是不会记录自身缓冲区大小的,所以 strcat 函数假定程序员在执行这个函数时,已经为 dest 分配了足够多的内存,可以容纳 src 字符串中的所有内容,而一旦这个假定不成立,就会发生缓冲区溢出可能会造成程序运行终止。
SDS 在修改字符串时会检查剩余空间(free
),如果不足则自动扩展,避免溢出。
减少内存重分配次数
SDS 采用预分配空间的策略:
- 当字符串长度增加时,SDS 会额外分配一些未使用的空间(
free
),减少后续修改时的内存重分配次数。 - 例如,SDS 的扩容策略通常是:新长度小于 1MB 时,分配双倍空间;大于 1MB 时,额外分配 1MB。
二进制安全
C 语言的原生字符串以 \0
作为结束符,不能存储包含 \0
的二进制数据。因此其只能保存文本数据,不能保存图片,音频,视频和压缩文件等二进制数据,否则可能出现字符串不完整的问题,所以其是二进制不安全。
SDS 通过 len
字段记录长度,可以存储任意二进制数据,包括 \0
。
兼容C语言字符串函数
SDS 的 buf
字段末尾会自动添加 \0
,因此可以直接使用 C 语言的字符串函数(如 printf
)。
SDS的操作
Redis 提供了一系列函数来操作 SDS,例如:
- 创建 SDS:
sdsnew
、sdsempty
- 追加字符串:
sdscat
- 复制字符串:
sdscpy
- 释放 SDS:
sdsfree
- 获取长度:
sdslen
- 获取剩余空间:
sdsavail
总结
SDS 是 Redis 中用于表示字符串的底层数据结构,具有高效、安全、灵活的特点。它通过预分配空间、记录长度等方式,避免了 C 语言原生字符串的缺陷,同时兼容 C 语言的字符串函数。SDS 的设计是 Redis 高性能和高可靠性的重要基础之一。