SDS (simple dynamic string)的结构
Redis底层使用 C 语言实现,但是没有直接使用 C 语言的传统字符串表示,而是自己实现了一个简单动态字符串(SDS)的类型。 在 Redis 中,几乎所有地方都使用这个数据结构来表示字符串
SDS 的结构如下
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
其中 buf 数组用于字符串的实际存储,保留了 C 语言中字符串以空字符结尾的要求,这样可以复用 C 语言中所有的函数和库,不需要重新实现。 len 和 free 分别记录了字符串的长度和剩余可用空间,这样在字符串长度变化时,可以快速计算出剩余空间,避免频繁的内存分配和释放。
二进制安全
C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,这样就保证了 SDS 是二进制安全的,可以保存任意的二进制数据。
内存预分配策略
Redis 的 SDS 实现了内存预分配策略,当 SDS 的剩余可用空间足够时,会直接在末尾追加新的字符串,而不需要重新分配内存。 当 SDS 的剩余可用空间不够时,会重新分配内存,并返回新的 SDS 结构体,这样避免了频繁的内存分配和释放。
具体的预分配策略如下:
- 当占用长度小于 1M 时,分配空间为占用长度的两倍
- 当占用长度大于 1M 时,分配空间为占用长度的 1M
惰性空间释放策略
Redis 的 SDS 实现了惰性空间释放策略,当 SDS 的剩余可用空间被释放时,不会立即释放内存,而是将剩余可用空间记录在 free 字段中,这样避免了频繁的内存分配和释放。
字符串对象的底层编码切换
在 Redis 中,即便你用的是 String 类型,底层也不一定直接是一个独立的 SDS 结构。Redis 会根据你存入的内容进行编码转换(Encoding):
-
int:如果你存入的是可以用 long 类型表示的整数,Redis 会直接把它转成长整型存起来,省去了复杂的字符串结构。
-
embstr:如果你存入的是短字符串(长度小于等于 44 字节),Redis 会把 RedisObject 头信息和 SDS 对象连续分配在同一块内存中。只需一次内存分配,对 CPU 缓存更友好。
-
raw:如果字符串长度大于 44 字节,Redis 会分别为 RedisObject 和 SDS 分配两块独立的内存空间。