1. 简单动态字符串
当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用SDS来表示字符串值,比如在Redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的。
举个例子,如果客户端执行命令:1
2redis> SET msg "hello world"
OK
那么Redis将在数据库中创建一个新的键值对,其中:
- 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串”msg”的SDS。
键值对的值也是一个字符串对象,对象的底层实现是一个保存着字符串”hello world”的SDS。
又比如,如果客户端执行命令:1
2redis> RPUSH fruits "apple" "banana" "cherry"
(integer) 3键值对的键是一个字符串对象,对象的底层实现是一个保存了字符串”fruits”的SDS。
- 键值对的值是一个列表对象,列表对象包含了三个字符串对象,这三个字符串对象分别由三个SDS实现:第一个SDS保存着字符串”apple”,第二个SDS保存着字符串”banana”,第三个SDS保存着字符串”cherry”。
1.1 SDS的定义
每个sds.h/sdshdr结构表示一个SDS值:
1 | struct sdshdr { |
图2-1展示了一个SDS示例:
- free属性的值为0,表示这个SDS没有分配任何未使用空间。
- len属性的值为5,表示这个SDS保存了一个五字节长的字符串。
- buf属性是一个char类型的数组,数组的前五个字节分别保存
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符对于SDS的使用者来说是完全透明的。遵循空字符结尾这一惯例的好处是,SDS可以直接重用一部分C字符串函数库里面的函数。
2。2 对c和sds之间区别进行总结
C字符串 | SDS |
---|---|
获取字符串长度的复杂度为O(N) | 获取字符串长度复杂度为O(1) |
API是不安全的,可能会造成缓冲区溢出 | API思安全的,不会造成缓冲区溢出 |
修改字符串长度N次必然需要执行N次内存重新分配 | 修改字符串长度N次最多需要执行N次内存分配 |
只能保持文本数据 | 可以保存文本或二进制数据 |
可以使用所有<string.h>库中的函数 | 可以使用一部分<string.h>库中的函数 |
SDS 主要API
函数 | 作用 | 时间复杂度 |
---|---|---|
sdsnew| 创建一个包含给定 C 字符串的 SDS 。 |O(N) , N 为给定 C 字符串的长度。
sdsempty| 创建一个不包含任何内容的空 SDS 。 |O(1)
sdsfree| 释放给定的 SDS 。 |O(1)
sdslen| 返回 SDS 的已使用空间字节数。 |这个值可以通过读取 SDS 的 len 属性来直接获得, 复杂度为 O(1) 。
sdsavail| 返回 SDS 的未使用空间字节数。 |这个值可以通过读取 SDS 的 free 属性来直接获得, 复杂度为 O(1) 。
sdsdup| 创建一个给定 SDS 的副本(copy)。 |O(N) , N 为给定 SDS 的长度。
sdsclear| 清空 SDS 保存的字符串内容。| 因为惰性空间释放策略,复杂度为 O(1) 。
sdscat| 将给定 C 字符串拼接到 SDS 字符串的末尾。 |O(N) , N 为被拼接 C 字符串的长度。
sdscatsds| 将给定 SDS 字符串拼接到另一个 SDS 字符串的末尾。 |O(N) , N 为被拼接 SDS 字符串的长度。
sdscpy| 将给定的 C 字符串复制到 SDS 里面, 覆盖 SDS 原有的字符串。 |O(N) , N 为被复制 C 字符串的长度。
sdsgrowzero| 用空字符将 SDS 扩展至给定长度。| O(N) , N 为扩展新增的字节数。
sdsrange| 保留 SDS 给定区间内的数据, 不在区间内的数据会被覆盖或清除。 |O(N) , N 为被保留数据的字节数。
sdstrim| 接受一个 SDS 和一个 C 字符串作为参数, 从 SDS 左右两端分别移除所有在 C 字符串中出现过的字符。 |O(M*N) , M 为 SDS 的长度, N 为给定 C 字符串的长度。
sdscmp| 对比两个 SDS 字符串是否相同。 |O(N) , N 为两个 SDS 中较短的那个 SDS 的长度。