Redis简单动态字符串SDS--Redis设计与实现的读书笔记

Redis简单动态字符串SDS

  • 1 SDS的定义
  • 2 SDS和C字符串区别
    • 2.1 常数复杂度获取字符串长度
    • 2.2 杜绝缓冲区溢出
    • 2.3 减少内存重分配次数
    • 2.4 二进制安全
    • 2.5 兼容部分C字符串函数
    • 2.6 区别总结
  • 3 SDS的API

参考资料:《Redis设计与实现》
Redis是利用C语言写的,但是Redis没有使用C语言传统的字符串表示(以空字符串结尾的字符数组,下面简称C字符串),而是自己构建了名为简单动态字符串(Simple Dynamic String,SDS)的抽象类型,作为Redis的默认字符串表示。
SDS除了可以表示Key和Value中的字符串外,还可以用作缓冲区(buffer),AOF的缓冲区,客户端状态的输入缓冲区,都是由SDS实现的。

1 SDS的定义

每个sds.h/sdsstr结构包含一个SDS值:

struct sdshdr {
	int len;
	int free;
	char buf[];
};

len是已使用字节的数量,free是未使用字节的数量,buf是字符数组。
SDS保留了以空字符串结尾的传统,好处是这样可以重用C字符串函数库中的函数,例如打印函数printf。

2 SDS和C字符串区别

2.1 常数复杂度获取字符串长度

C字符串不记录自身长度信息,每次获取字符串长度必须遍历字符串,时间复杂度为O(N)。
SDS属性中记录了字符串长度len,因此时间复杂度为O(1)。
设置和更新len是SDS的API自动完成的,使用SDS无需任何修改。

2.2 杜绝缓冲区溢出

C字符串由于没有记录字符串长度,因此如果程序猿不小心,可能导致缓冲区溢出(buffer overflow),例如strcat函数可以把后一个字符串拼接到前一个字符串末尾,但是这假设前一个字符串已经分配了足够的空间,一旦假设不成立,就会发生缓冲区溢出。
SDS的空间分配策略完全杜绝了缓冲区溢出的可能性,当SDS的API对字符串进行修改时,API会先检查字符串空间是否足够,如果不够,会自动拓展空间至修改所需大小,然后执行实际修改。

2.3 减少内存重分配次数

C字符串如果包含十个字符,那么底层是十一个字符长的数组,每次增长或缩短字符串,都要对字符串进行内存重分配。

  1. 如果增长字符串,例如append,先通过内存重分配拓展底层数组的大小,再执行操作,否则会缓冲区溢出
  2. 如果缩短字符串,例如trim,执行操作后需要通过内存重分配来释放不再使用的空间,否则会内存泄露

SDS实现了空间预分配和惰性空间释放两种分配策略:

  1. 空间预分配
    如果需要对SDS进行拓展,不仅会为SDS分配所需要空间,还会分配额外空间。遵循以下两个公式:如果分配后len小于1MB,那么程序会额外分配相同的未使用空间,即len和free一样。如果分配后len大于1MB,例如len20MB,那么会分配1MB未使用空间。这样减少了内存重分配的次数。
  2. 惰性空间释放
    当SDS的API需要缩短字符串时,程序并不会立即使用内存重分配来回收空间,而是使用free属性记录未使用的空间,并等待将来使用。同时,SDS也提供了相应的API,让我们在有需要时真正地释放未使用空间,不用担心惰性空间释放策略导致内存浪费。

2.4 二进制安全

C字符串只能保存文本数据,不能保存像图片、音频、视频这样的二进制文件。这是因为C字符串必须符合某种编码(例如ASIIC码),而且除了结尾外,不能包含空字符串,否则会在中间截断只取前面部分。
SDS是二进制安全的,所有SDS的API都会以二进制的方式来处理存放在buf中的数据,程序不会作任何限制、过滤、或假设,数据在写入时什么样,在读取的时候就什么样。
这也是为什么buf属性称为字节数组的原因,Redis不是用这个属性来保存字符串,而是用这个属性来保存二进制数据。因此Redis不仅可以保存文本数据,还可以保存任意格式的二进制数据。

2.5 兼容部分C字符串函数

前面已经提到,SDS遵循了C字符串的传统,以空字符结尾,这是为了让那些保存文本数据的SDS可以使用一部分C字符串函数库()中的函数,避免了不必要的代码重复。

2.6 区别总结

C字符串 SDS
获取字符串长度的时间复杂度为O(N) 获取字符串长度的时间复杂度为O(1)
API是不安全的,又可能造成缓冲区溢出 API是安全的,不会造成缓冲区溢出
修改N次字符串必须执行N次内存重分配 修改N次字符串最多执行N次内存重分配
只能保存文本数据 可以保存文本或者二进制数据
可以使用所有中的函数 可以使用部分中的函数

3 SDS的API

函数 作用 时间复杂度
sdsnew 创建一个包含给定C字符串的SDS O(N),N为给定C字符串的长度
sdsempty 创建一个不包含任何内容的空SDS O(1)
sdsfree 释放给定的SDS O(N),N为被释放SDS的长度
sdslen 返回SDS的已使用空间字节数 O(1)
sdsavail 返回SDS的未使用空间字节数 O(1)
sdsdup 创建一个给定SDS的副本 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为给定C字符串的长度
sdstrim 接受一个SDS和一个C字符串作为参数,移除所有在C字符串中出现过的字符 O(N2),N为给定C字符串的长度
sdscmp 对比两个SDS字符串是否相同 O(N),N为两个SDS中较短的那个SDS的长度

你可能感兴趣的:(数据库,redis)