Sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示。
Sds 在 Redis 中的主要作用有以下两个:
- 实现字符串对象(StringObject);
- 在 Redis 程序内部用作
char*
类型的替代品;
Redis 是一个键值对数据库(key-value DB), 数据库的值可以是字符串、集合、列表等多种类型的对象, 而数据库的键则总是字符串对象。
对于那些包含字符串值的字符串对象来说, 每个字符串对象都包含一个 sds 值。(在 Redis 中, 一个字符串对象除了可以保存字符串值之外, 还可以保存 long
类型的值, 所以为了严谨起见, 这里需要强调一下:当字符串对象保存的是字符串时, 它包含的才是 sds 值, 否则的话, 它就是一个 long
类型的值。)
sds 的实现
在前面的内容中, 我们一直将 sds 作为一种抽象数据结构来说明, 实际上, 它的实现由以下两部分组成:
typedef char *sds;
struct sdshdr {
// buf 已占用长度
int len;
// buf 剩余可用长度
int free;
// 实际保存字符串数据的地方
char buf[];
};
其中,类型 sds
是 char *
的别名(alias),而结构 sdshdr
则保存了 len
、 free
和 buf
三个属性。
作为例子,以下是新创建的,同样保存 hello world
字符串的 sdshdr
结构:
struct sdshdr {
len = 11;
free = 0;
buf = "hello world\0"; // buf 的实际长度为 len + 1
};
当执行 APPEND 命令时,相应的 sdshdr
被更新,字符串 " again!"
会被追加到原来的 "hello world"
之后:
struct sdshdr {
len = 18;
free = 18;
buf = "hello world again!\0 "; // 空白的地方为预分配空间,共 18 + 18 + 1 个字节
}
sds.c/sdsMakeRoomFor
函数描述了 sdshdr
的这种内存预分配优化策略, 以下是这个函数的伪代码版本:
def sdsMakeRoomFor(sdshdr, required_len):
# 预分配空间足够,无须再进行空间分配
if (sdshdr.free >= required_len):
return sdshdr
# 计算新字符串的总长度
newlen = sdshdr.len + required_len
# 如果新字符串的总长度小于 SDS_MAX_PREALLOC
# 那么为字符串分配 2 倍于所需长度的空间
# 否则就分配所需长度加上 SDS_MAX_PREALLOC 数量的空间
if newlen < SDS_MAX_PREALLOC:
newlen *= 2
else:
newlen += SDS_MAX_PREALLOC
# 分配内存
newsh = zrelloc(sdshdr, sizeof(struct sdshdr)+newlen+1)
# 更新 free 属性
newsh.free = newlen - sdshdr.len
# 返回
return newsh
在目前版本的 Redis 中, SDS_MAX_PREALLOC
的值为 1024 * 1024
, 也就是说, 当大小小于 1MB
的字符串执行追加操作时, sdsMakeRoomFor
就为它们分配多于所需大小一倍的空间; 当字符串的大小大于 1MB
, 那么 sdsMakeRoomFor
就为它们额外多分配 1MB
的空间。
小结:
- Redis 的字符串表示为
sds
,而不是 C 字符串(以\0
结尾的char*
)。 - 对比 C 字符串,
sds
有以下特性:- 可以高效地执行长度计算(
strlen
); - 可以高效地执行追加操作(
append
); - 二进制安全;
- 可以高效地执行长度计算(
sds
会为追加操作进行优化:加快追加操作的速度,并降低内存分配的次数,代价是多占用了一些内存,而且这些内存不会被主动释放。
摘抄:http://redisbook.readthedocs.io/en/latest/internal-datastruct/sds.html