什么是动态字符串(SDS)
动态字符串(Simple Dynamic String)是Redis五大数据结构之一。
SDS在Redis中有以下作用
- 作为Redis中的字符串对象
- 替代C语言中的char*类型
为什么需要SDS替代char*
SDS相比于char*有以下优势
- 支持O(1)的查询字符串长度
strlen(s)
操作 - 修改字符串时对于内存的重分配较少
- 二进制安全
- 杜绝缓冲区溢出
O(1)的strlen()
SDS中是通过一个len字段存储字符串长度的。
它不以' '决定字符串结尾
不需要遍历字符串来得到字符串长度
typedef char* sds
struct sdshdr{
unsigned int len;
unsigned int free;
char* buf;
}
内存重分配更少
从上面的结构中可以发现,SDS还有个free字段。
这个字段是用于内存预分配和惰性释放的,用于存储预分配内存后字符串的额外长度。
C语言中n个字符的字符串势必要n+1个连续空间存储,每次修改都需要对这个字符数组重新分配内存。
redis为了优化内存重分配次数,采取了这种策略。
修改字符串时,会对字符串空间进行预分配
- 如果修改后的字符串长度大于原来分配的字符串长度,那么会基于修改后字符串长度,给SDS分配额外内存(SDS_MAX_PREALLOC在redis中为1MB)
- after_modified.len < SDS_MAX_PREALLOC, 额外分配after_modified.len(free中存储额外长度)
- after_modified.len >= SDS_MAX_PREALLOC,额外分配SDS_MAX_PREALLOC
- 如果修改后的字符串长度小于于原来分配的字符串长度,直接用预分配好的内存
sds sdsMakeRoomFor(sds s, const char *t, size_t len)
{ ...
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else newlen += SDS_MAX_PREALLOC;
newsh = realloc(sh, sizeof(struct sdshdr) + newlen + 1);
if (newsh == NULL)
return NULL;
newsh->free = newlen - len; return newsh->buf;
}
这种策略在当字符串长度不断缩减时,可能会导致一个问题: 字符串占用了过多不必要的内存
但Redis也为其设计了相应策略
惰性空间释放
上面暗示了,redis不会在字符串长度减少时对多余空间立即释放,那么什么时候会释放不必要的多余空间呢?
当SDS的字符串占用率低于%25时
即free>3*len时,
SDS会释放掉原来size的(即free+len)的%50的大小,即缩一半
杜绝缓冲区溢出
SDS 的空间分配策略完全杜绝了发生缓冲区溢出的可能性,具体的实现在
sds.c
中。通过阅读源码,我们可以明白之所以 SDS 能杜绝缓冲区溢出是因为再调用sdsMakeRoomFor
时,会检查 SDS 的空间是否满足修改所需的要求(即free >= addlen
条件),如果满足 Redis 将会将 SDS 的空间扩展至执行所需的大小,在执行实际的 concat 操作,这样就避免了溢出发生
二进制安全
C 字符串必须符合某种编码,并且除了字符串的末尾之外,字符串不能包含空字符(