概述
众所周知,Redis是使用C语言实现,那么Redis中字符串是否直接使用C字符串呢?答案是:否。
Redis构建了一种简单动态字符串(Simple Dynamic String, SDS),并使用SDS作为内部默认字符串类型(下文提到Redis动态字符串都用SDS表示),同时Redis也会在对字符串无需修改的地方使用C字符串,例如:打日志。
定义
图中展示是用SDS的结构体,包含了三个属性值:SDS长度、未使用字节数量、字节数组。
- free 属性记录buf数组未使用字节数量,0代表SDS没有分配任何未使用的字节数量。
- len属性值buf数组中已使用的字节数量,5代表SDS表示当前保存字符串的长度
- buf[] 属性是用于保存字符串数组,char类型数组。
图中我们可以看出,SDS遵循C字符串以“空串”结尾的惯例,目的是为了可以重用C字符串库中的函数,例如打印操作:prinf(s->buf);
特性
常数复杂度获取字符串长度
- 相比较C字符串,SDS会提供一个属性值len来记录字符串长度,不需要从头到尾遍历一遍对字符进行计数,因此时间复杂度可以从O(n)降到O(1)。
杜绝缓冲区溢出
- 因为C字符串不记录自身长度,导致C中strcat函数在做字符串拼接的时候,原字符串没有足够的内存导致内存溢出;当SDS API对SDS进行修改时,会先检查当前空间是否满足需求,如果不满足会先扩展SDS空间,然后再执行拼接操作。
减少字符串修改时带来的内存重分配次数
-
空间预分配: SDS会在修改字符串同时不仅会分配必须的空间,还会分配相同大小额外未使用的空间。如果对SDS修改后,SDS的大小小于1M,这时SDS的free属性将和len属性保持一致。如果修改后SDS的大小大于1M,此时free属性将保持1M不会变化。
例如:修改之后SDS大小变为13个字节,此时len属性大小为13,free属性大小为13,此时buf数组的实际长度为13+13+1 = 27 -
惰性空间释放:当SDS需要缩短字符串时,并不会立即回收缩短后的内存,而是使用free属性将这些记录起来,并供将来使用。
兼容部分C串函数
- 为了可以尽可能多重用C库中操作字符串的函数,SDS遵循了C字符串以空串结尾的惯例,这样Redis就不需要单独的实现操作SDS的函数了,避免了不必要的重复代码工作。
二进制安全
- 因为SDS有记录长度的len属性,这可以使SDS不需要使用空串来判断当前是否结束。SDS里面保存数据的二进制格式,程序不会对数据进行限制或者过滤,因此是二进制安全的数据类型。
相比较C字符串,SDS可以保存除了文本以外数据,例如:图片、音频、视频、压缩文件。
总结
下面是针对C字符串和SDS的区别做了一些总结:
C字符串 | SDS |
---|---|
获取字符串长度的时间复杂度为O(n) | 获取字符串长度的时间复杂度为O(1) |
API不安全,可能造成缓存溢出 | API安全,不会造成溢出 |
修改字符串N次必然需要内存重分配N次 | 不一定需要N次,最坏情况下为N次 |
只能保存文本数据 | 可以保存文本和任意二级制数据 |
占用空间小 | 占用空间大 |
参考资料
《Redis设计与实现》第二章 简单动态字符串
能力有限,如果文中存在不妥当的内容,恳请及时指正!