Redis 简单动态字符串 SDS
简单动态字符串
Redis 是用 c 语言编写的,但是 Redis 没有用 c 语言传统的字符串表示(以空字符串结尾的字符数组),而是自己构建了简单动态字符串SDS (simple dynamic string) 的类型。
Redis 默认用 SDS 表示字符串。
当 Redis 需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis 就会用 SDS 表示字符串,在Redis 中 包含字符串的键值对底层都是用SDS实现的。
SDS 的定义
struct sdshdr { // buf 已占用长度 int len; // buf 剩余可用长度 int free; // 实际保存字符串数据的地方 char buf[]; };
- len表示字符串的长度。
- free表示buf中剩余可用长度
- buf是存储字符串数组的地方
SDS 遵循 c 语言以空格结尾的惯例,这么做的好处是SDS 可以重用一部分 c 语言字符串函数库里面的函数。结尾的空格不计在len属性中。
下图展示了一个存入 Hello 的SDS结构:
SDS 与 c 字符串
c 语言字符串使用N+1长度的字符数组存储长度为N的字符串,并且字符串最后一位是 '\0'。如下图:
下面讨论Redis为什么使用SDS来表示默认的字符串,以及两个的区别。
常数复杂度获取字符串长度
因为 c 语言字符串不记录自身长度信息,所以要获取字符串长度需要遍历整个字符产,直到遇到 '0\' 停止。时间复杂度为O(N)
但是对于 SDS 来说,自身记录字符串长度信息,所以获取字符串长度时间复杂度为O(1)
所以 Redis 使用SDS,使获取字符串的长度操作只需O(N)复杂度,大大的提高了系统的性能。
杜绝缓冲区溢出
c 语言中使用 <string.h>/strcat 函数拼接字符串。函数定义如下:
char *strcat(char *dest, const char *src);
strcat 函数可以把 src 内容拼接到 dest 末尾,strcat 函数假定执行时,dest 后面又足够的空间容纳 src 中的字符串,而这个假定不成立时就有可能造成缓冲区溢出。
如下图例子,假设程序内存中有紧邻着的字符串s1和s2,其中s1为 Hello s2为 World:
这时,程序执行一下代码:
strcat(s1, "Golang")
s1的内容改为 "HelloGolang",但是没有给s1分配足够的空间,导致s1的内容溢出到s2中,导致s2的内容被修改:
而SDS的空间分配策略,杜绝了缓冲区溢出。当SDS API 需要修改SDS时,API会先检测SDS的空间是否满足修改要求,如果不满足,API会自动将SDS空间扩容至合适的大小,然后才执行修改操作。
而这个扩容也是有策略的,下一节将提到。
修改字符串时减少内存分配次数
对于 c 语言来说,每次修改字符串都需要从新分配内存
- 增长字符串
比如 append 操作,程序需要先分配内存来扩容底层数组空间,如果没有,会导致缓冲区溢出。
- 缩短字符串
比如 trim 截断操作,那么程序需要释放截断部分的内存,如果没有,会导致内存泄漏。
由于 Redis 是数据库,会经常有数据修改的场合,如果每次修改都要重新分配内存,释放内存,会对性能造成影响
为了避免 c 字符串这种缺陷,SDS 实现了以下策略:
空间预分配
空间预分配用于优化SDS的字符产增长操作,当SDS API 对SDS 字符串进行增长操作,并且需要对SDS空间进行扩容时,程序不仅会分配所需的空间,还会为SDS分配所谓空间额外空间。
其中额外分配空间由一下规则决定:
- 修改SDS后,SDS长度(len属性)小于1MB
那么额外分配的空间大小等于分配的大小,即 len == free
- 修改SDS后,SDS长度(len属性)大于1MB
那么额外分配的空间大小等于1MB.
通过这样的策略,可以减少空间分配次数,把改N次优化为最多改N次。
惰性空间释放
惰性空间释放用于优化SDS的字符串截断操作,当SDS API 对 SDS字符串进行截断操作时,SDS并不会释放空间,而是用free属性存储起释放空间的大小,等待将来使用。
下一次扩容时直接使用这部分空间,减少内存重新分配次数。
二进制安全
c 语言中,字符串必须符合特定的编码,并且除了尾部的空格外,中间不能包含空格,否则遇到空格程序会以为到了尾部。这使得 c 字符串只能存文本文件。
但是SDS是由len属性判断是否字符串是否结束,所以SDS可以存二进制文件
部分兼容 c 字符串函数
c 字符串要求字符数组的末尾必须是\0,作为字符串尾的标记。而SDS中的字符数组也遵循了这一规范,所以仍然可以使用C字符串相关函数,因此避免了重复代码。
参考文献
redis 设计与实现(第二版)
https://www.jianshu.com/p/1b814c3173f1