目录
Redis构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,并将SDS作为Redis的默认字符串表示。
SDS的定义
struct sdshdr {
// 记录buf数组中已使用字节的数量,不包括'\0'的长度
// 等于SDS中保存的字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
SDS与C字符串的区别
获取字符串的长度
C字符串不记录自身的长度信息, 获取字符串长度时会遍历字节数组, 直到遇到空字符为止. 复杂度为 O(N)
SDS直接通过 len 属性获取字符串长度. 复杂度为O(1)
杜绝缓冲区溢出
C字符串不记录自身长度, 修改字符串时不会判断本身是否拥有足够的内存空间, 当内存空间不足时, 则会造成缓冲区的溢出.
SDS对字符串进行修改时,先检查内存空间是否满足修改的需要, 若不满足, 则自动扩展SDS的内存空间. 所以使用SDS既不需要手动修改内存空间的大小, 也不会出现缓冲区溢出的情况.
空间预分配
第一次创建字符串对象时, SDS不会分配冗余空间, 即 len = 0
当SDS的API修改SDS时, 则会为其分配冗余空间.
- 当修改后的SDS的 len 属性小于1MB时, 则为其分配和 len 同样大小的冗余空间, 即 free = len, 此时 buf [ ] 的实际长度 = len(实际长度) + free(冗余空间) + 1(空字符)
- 当修改后的SDS的 len 属性大于等于1MB时, 则为其分配1MB的冗余空间. buf [ ] 的实际长度 = len(实际长度) + free(1MB) + 1(空字符)
惰性空间释放
SDS的API缩短SDS的字符串时, 不会立即使用内存分配回收缩短后多出来的字节, 而是记录在 free 属性中, 并等待将来使用.
二进制安全
C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
SDS的API都是二进制安全的.所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,它被读 取时就是什么样。
这也是我们将SDS的buf属性称为字节数组的原因——Redis不是用这个数组来保存字符,而是用它来保存一系列二进制数据。
区别汇总
C字符串 | SDS |
获取字符串长度的复杂度为O(N) | 获取字符串长度的复杂度为O(1) |
API是不安全的,可能会造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 |
修改字符串N次必然需要执行N次内存重新分配 | 修改字符串N次最多需要执行N次内存重新分配 |
只能保存文本数据 | 可以保存文本或者二进制数据 |
可以使用所有<string.h>库中的函数 | 可以使用一部分<string.h>库中的函数 |
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的副本(copy) | O(N) |
sdsclear | 清空SDS保存的字符串内容 | O(1) |
sdscat | 将给定C字符串拼接到SDS字符串的末尾 | O(N) |
sdscatsds | 将给定SDS字符串拼接到另一个SDS字符串的末尾 | O(N) |
sdscpy | 将给定的C字符串复制到SDS里面,覆盖SDS原有的字符串 | O(N) |
sdsgrowzero | 用空字符将SDS扩展至给定长度 | O(N) |
sdsrange | 保留SDS给定区间内的数据,不在区间内的数据会被覆盖活清除 | O(N) |
sdstrim | 接受一个SDS和一个C字符串作为参数,从SDS左右两端分别移除所有在C字符串中出现过的字符 | O(M*N) |
sdscmp | 对比两个SDS字符串是否相同 | O(N) |