redis
中对string
值的存储,并没有使用C中的char*
,而是使用了SDS (simple dynamic string)
SDS
结构类似Go
里的切片,有3
个字段,总长度alloc
(类似capacity
),已用长度len
,以及实际字符串数组char*
- 相对于
C
的char*
,有如下优势:- 二进制安全的(不用原字符串 +
\0
的方式表示结束) - 能在
O(1)
的时间获取字符串长度 - 预留了额外空间,追加字符串时可能不需要重新分配空间;(惰性释放,内存紧张时也会释放)
- 二进制安全的(不用原字符串 +
// 空间占用为3B + 字符串长度
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 已使用的长度 used */
uint8_t alloc; /* 分配的总长 excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
SDS
的实现有2
种,在长度特别短时,使用embstr
形式存储,而当长度超过44
字节时,使用raw
形式存储。- 两者其实都是
header
+char*
的组合。 embstr
比raw
少一次内存空间分配,它把header
和 字符串保存在同一块连续的内存里了。而raw
要申请额外内存保存字符串。
为啥是
44
字节?
64
字节,减去RedisObject
头信息19
字节,再减去3
字节SDS
头信息,再减去\0
结尾,这样最后可以存储44
字节。
新字符串长度 = (原有长度 + 新长度)+ 预分配空间
预分配空间的算法如下:
sds sdsMakeRoomFor(sds s, size_t addlen) {
newlen = (len+addlen); // 追加后的长度
if (newlen < SDS_MAX_PREALLOC) // 1MB
newlen *= 2; // 上次用了len长度的空间,那么下次程序可能也会用len长度的空间,所以redis就为你预分配。这个有点谜
else
newlen += SDS_MAX_PREALLOC;
}