简单动态字符串
简单动态字符串(simple dynamic string, SDS),是由 Redis
构建的一种抽象类型。Redis
中默认字符串表示就是使用简单动态字符串。
当需要一个无须进行修改的字面量时,就会使用 C 语言中的传统字符串。如打印日志:
redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
当需要一个可以被修改的字符串值时, Redis
就会使用 SDS
来表示此字符串值。如 Redis
数据库中,包含字符串的键值对就是由 SDS
实现。
redis> SET msg "hello world"
OK
SDS 的定义
SDS
结构如下:
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
下面用一个 SDS
示例来帮助大家更好的理解其结构:
free
的值为 0,表示这个SDS
没有任何未使用空间len
的值为 5,表示这个SDS
保存了一个 5 字节长的字符串buf
是一个char
类型的数组,数组的前 5 个字节分别保存了h
,e
,l
,l
,o
,s
,最后一个字节保存了空字符\0
SDS
遵循 C 语言字符串以空字符结尾的惯例,保存空字符的 1 字节的空间不计算在 SDS
的 len
属性中。而额外分配 1 字节空间、添加空字符到字符串末尾等操作,都是由 SDS
函数自动完成,所以这个空字符对使用者来讲是完全透明的。遵循空字符结尾的好处是 SDS
可以直接重用一部分 C 语言字符串的函数库里面的函数,如:使用 <stdio.h>
中的 printf
函数进行打印,
printf("%s", s->buf);
SDS 与 C 语言字符串的区别
获取字符串长度的复杂度为常数级别
因为 SDS
中的 len
属性记录了本身的长度,所以获取长度的复杂度为 O(1)。而 C 语言字符串获取长度的复杂度是 O(n)
,因为只有遍历到结尾的空字符时,才能知道长度。
不存在缓存溢出的问题
SDS
的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当需要对 SDS
进行修改时,相关 API
会先检查 SDS
空间是否满足修改所需的需求,如果不满足的话,API
会自动将 SDS
的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以 SDS
不需要手动修改 SDS
的空间大小,也不会出现缓冲区溢出问题。简单来说,空间不满足,自动扩容。
减少修改字符串时引发的内存重分配次数
SDS
通过未使用空间解除了字符串长度和底层数组长度之间的关联:在 SDS
中,buf
数组的长度不一定就是字符数量加 1,数组里面可能包含未使用的字节,这些字节的数量由 SDS
的 free
属性记录。
通过未使用空间,SDS
实现了空间预分配和惰性空间释放两种优化策略。
空间预分配
用于优化 SDS
的增长操作。当需要对 SDS
进行空间扩展时,除了分配修改所必需的空间,还会分配额外的空间。额外分配的空间由以下公式决定:
- 如果对
SDS
操作后,SDS
的长度(len
的值)小于1M
,那么程序分配和len
同样大小的未使用空间。 - 如果对
SDS
操作后,SDS
的长度(len
的值)大于等于1M
,那么程序会分配1M
的未使用空间。
通过空间预分配策略,SDS 将连续增长 N 次字符串所需的内存重分配次数从必定 N
次降为最多 N
次。
惰性空间释放
用于优化 SDS
的字符串缩短操作:当 需要缩短 SDS
保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用 free
属性将这些字节的数量记录起来,并等待将来使用。同时,SDS
也提供了 API
,让我们在有需要时,真正释放 SDS 未使用的空间,所以不用担心会造成内存浪费。
通过惰性空间释放策略,避免了缩短字符串时的内存重分配操作,并为将来可能有的增长操作提供了优化。
二进制安全
C 语言字符串中的字符必须符合某种编码(比如 ASCII
),并且除了字符串的末尾之外,字符串里不能包含空的字符,否则最先被程序读入的空字符会被认为是字符串的末尾,这些限制使得 C 字符串只能保存文本数据,而不能保存 像图片、音频、视频、压缩文件这样的二进制数据。所有的 SDS
操作都是二进制安全的,都会以处理二进制的方式来处理存放在 buf
数组里的数据,程序不会对其中的数据做任何限制、过滤或者假设,数据在写入时什么样,那么在读取时就是什么样。
兼容部分 C 语言字符串函数
通过遵循 C 语言字符串以空字符为结尾的惯例,SDS
可以在有需要时重用 <string.h>
函数库,从而避免了代码重复。
本节回顾
Redis
只会在字面量使用 C 语言字符串。其他情况下,Redis
使用 SDS
作为字符串表示。
比起 C 语言字符串,SDS
具有以下优点:
- 获取字符串长度的复杂度为常数级别
- 不存在缓存溢出的问题
- 减少修改字符串时引发的内存重分配次数
- 二进制安全
- 兼容部分 C 语言字符串函数