前言
Redis是c语言开发,但在Redis中存储字符时却并未使用c字符串,而是自定义了一种字符串类型,叫做简单动态字符串(simple dynamic string, SDS)。之所以要“多此一举”,主要原因在于Redis面对的是大量存取的应用场景,且对速度往往较为严苛。
定义
-
c语言的字符串就是用N+1个字符数组来存取数据,最后一个元素是空字符’\0’,如下所示:
-
而Redis中的SDS则在此基础上定义了一个数据结构,主要由三部分构成
- 未使用空间的数量free
- 已使用空间的数量len
- char类型的数组(最后一个字节也是保存的空字符’\0’)
下面来讲讲为啥Redis要这样定义她的字符串。
SDS的好处
获取字符串长度时速度更快
-
这个好理解,c语言里没有存字符串的长度,所以在获取字符串的长度时必须遍历整个字符串
-
而SDS里存了字符串的长度,直接获取就行了
避免缓冲区溢出
假设有两个字符串,name=“zhangsan”,action=“eat”。当要将action拼接到name后面时,c语言与SDS的处理方式如下
-
c语言中字符串没有记录自身长度,若name中剩余长度小于action的长度,则很有可能造成缓冲区溢出
-
而SDS在对字符串进行修改之前,会先检查free的长度是否能满足,若太小则自动扩展到足够的大小,这样就
杜绝了发生缓冲区溢出的可能性
减少字符串修改时内存的重分配次数
c语言在每次进行字符串修改(增长或缩短)操作时,必定会对内存进行重新分配。而在这一过程中有可能会产生以下两个问题:
- 缓冲区溢出。在增长字符串时若没有对字符串进行内存重分配,则空间会不够用。
- 内存泄漏。在缩短字符串时没有进行内存重分配以回收多余空间
而SDS则可以减少内存重分配的次数,主要有以下两种方式:
空间预分配
-
这个就是在增长字符串时除了分配它所需要的长度空间之处,还多分配一点给它。
-
比如说将action="eat"添加到name="zhangsan"后面,分配给name的空间由原来的11个字节变为6 + 6 + 11,
总共23字节。(第一个6是action的长度,11是name的长度,另一个6则是多分配的空间)
下次要增长的时候,如果长度小于action的长度,则不需要执行空间预分配了。
惰性空间释放
-
惰性空间释放的意思就是在缩短字符串时不会立即释放空间,而是留着,并把长度加给free这个属性。
-
等到下次要增长的时候直接用就可以了。
无论是空间预分配还是惰性空间释放,都是以一点点”内存浪费“的代价来换取保存的速度增长。
二进制安全
-
c语言使用空字符’\0’来标识字符串是否结束。这样的话要是存二进制的数据有可能会被截断,
所以c语言不能用来存二进制数据
-
而SDS是用len属性来标识字符串是否结束的,所以SDS可以存二进制的数据
兼容部分c字符串函数
SDS里的buf数组最后也是用空字符串’\0’来结尾的,这样就能使用一部分c字符串的函数了。
总结
Redis要的是速度,设计SDS这种数据结构的主要目的就是提高存取速度。