Redis底层数据结构SDS

前言

Redis是C语言开发的,C语言的字符类型是char,字符数组是char[],Redis没有直接使用C语言的字符串类型,而是在其之上构建动态字符串(SDS)的抽象类型。

SDS代码

Redis3.2之前实现方式

struct sdshdr{
 int len;
 int free;
 char buf[];
}
len表示buf数组长度,free表示数组未使用长度。
而3.2之后扩展为5种类型,主要为了优化len和free这两个字段所占的空间,但实现思想上不变。

SDS比起C语言字符串优点

例如存储值为 "haha123"的字符串
C语言字符串: char data[] = "haha123\0"
sds:struct{ len: 7, free: 14, buf: "haha123\0" }

优点1:计数更快
C语言对字符串长度的统计,就完全来自遍历,从头遍历到末尾,直到发现空字符就停止,时间复杂度为O(n)。
而sds本身就保存了长度的信息,所以获取长度的时间复杂度为0(1)。

优点2:杜绝缓冲区溢出
调用C语言的字符串拼接函数,如果没有提前计算好内存,拼接后会产生缓冲区溢出的。
如果使用sds,它存储了当前长度len,还有free未使用的长度,做拼接操作,先判断是否可以放得下,如果长度够就直接执行,如果不够,那就进行扩容,再进行拼接。

优点3:减少修改字符串时带来的内存重分配次数
当频繁对字符串进行添加时,C语言需要不断开辟新的内存空间以存放新的字符串,频繁内存分配是很耗时的。Redis为了避免C字符串这样的缺陷,就分别采用了两种解决方案,去达到性能最大化,空间利用最大化。

  1. 空间预分配:当我们对SDS进行扩展操作的时候,Redis会为SDS分配好内存,并且根据特定的公式,分配多余的free空间,还有多余的1byte空间(这1byte也是为了存空字符),这样就可以避免我们连续执行字符串添加所带来的内存分配消耗。
    例如新建sds结构存储长度为7的字符串,那么此时len=7,free也是7,当需要拼接一个长度<=7的字符串,就可以例如free的空间直接进行拼接,省去内存重新分配。
    当扩容时,如果需要内存值小于1MB,那么会进行成倍的扩容,如果大于1MB,那么每次扩容增加1M,直到够为止。

  2. 惰性空间释放:刚才提到了会预分配多余的空间,如果一直不需要,可能会带来内存的泄露或者浪费,Redis也做了设计,当执行完一个字符串缩减的操作,Redis并不会马上收回我们的空间,因为可以预防你继续添加的操作,这样可以减少分配空间带来的消耗,但是当你再次操作还是没用到多余空间的时候,Redis也还是会收回对于的空间,防止内存的浪费的。

优点4:二进制安全
Redis作为缓存中间件,面向各种客户端通过socket使用,这些客户端数据经常会穿插空字符在中间,比如图片,音频,视频,压缩文件的二进制数据,如果直接使用C语言字符串库去读取,就会造成数据丢失。
而Redis就不存在这个问题了,因为SDS保存了字符串的长度,他不判断空字符,他就判断长度对不对就好了,所以Redis也经常拿来保存各种二进制数据,例如用来保存小文件的二进制。

优点5:兼容C语言的函数库
sds的buf结尾依然会添加'\0'作为结束,这样可以兼容C语言的字符串函数库使用。

你可能感兴趣的:(Redis底层数据结构SDS)