Redis的数据结构之SDS
Redis没有使用C语言风格的字符串, 而是使用了一种叫SDS的简单动态字符串。
C风格的字符串用来作为字符串字面量, 比如打印日志。当表示一个可以被修改的字符串的时候, redis会用SDS表示。
redis是key_value型数据库, 它的key和value都是一个对象。
SDS除了保存数据库中的字符串外还用作缓冲区(buffer): AOF模块中的AOF缓冲区, 以及客户端状态中的输入缓冲区。
SDS的定义
//sds.h/sdshdr 结构表示一个SDS值
struct sdshdr {
int len; //记录buf中使用的字节数量
int free; //记录还未使用的字节数量
int buf[]; //字节数组用来保存字符串
};
需要注意的是: buf中的最后有’\0’, 并且这个字符没有计入len中, 这样设计的原因是为了遵循C语言的标准, 这样就可以使用C语言的一些函数. 比如可以直接 printf("%s", s->buf);
SDS和C语言字符串的区别
-
常数时间复杂度获取字符串长度
C语言使用一个N+1长度的数组表示长度为N的字符串, 求C语言字符串长度需要遍历整个数组, 时间复杂度为O(N). 而 SDS 使用结构体中的len字段可以实现O(1)的时间复杂度求字符串的长度.
-
杜绝缓冲区溢出
C语言不记录字符串长度, 如果拼接字符串的时候分配的内存空间不够的话就会造成缓冲区溢出. 而SDS记录未使用的缓存区空间(free字段), 所以在拼接字符串的时候就会先检查内存够不够, 不够的话就扩展内存再进行拼接操作.
-
减小修改字符串时带来的内存重新分配次数
SDS的free字段会对SDS的空间进行预分配, 这样就可以避免像C语言字符串那样改变时需要重新分配内存的尴尬.
-
二进制安全
C语言字符串中字符必须符合某种编码, 除了字符串末尾之外不能包含空格, 否则容易被当做字符串结尾.这样就限制了保存二进制的能力.
而redis为了确保可以在各种不同的场景中使用, Redis的API都是二进制安全的. 也就是说写进去什么样, 读出来就是什么样.
-
兼容部分C语言字符串
SDS以’\0’结尾, 所以可以复用C语言一些现成的函数, 比如strcasecmp函数.
SDS的主要API
函数 | 作用 | 时间复杂度 |
---|---|---|
sdsnew | 创建一个包含给定C字符串的SDS | O(N) |
sdsempty | 创建一个空的SDS | O(1) |
sdsfree | 释放给定的SDS | O(N) |
sdslen | 返回SDS已经使用过的空间字符数 | O(1) |
sdsavail | 返回SDS中未使用的空间字符数 | O(1) |
sdsdup | 创建一个给定SDS的副本 | O(N) |
sdsclear | 清空SDS中字符串保存的内容 | O(1) 惰性空间释放 |
sdscat | 将C字符串拼接到SDS字符串末尾 | O(N) |
sdscatsds | 将SDS拼接到另一个SDS中 | O(N) |
sdscpy | 将给定的C字符串复制并覆盖到SDS中的字符串 | O(N) |
sdsgrowzero | 用空字符将SDS扩展至给定的长度 | O(N) |
sdsrange | SDS区间内的数据保留, 区间之外的数据覆盖或清除 | O(N) |
sdstrim | 传SDS和C字符串, 移除SDS左右两边分别在C出现的字符 | O(M*N) |
sdscmp | 比较两个SDS是否相等 | O(N) |