Redis底层数据结构——SDS

SDS 是什么

Redis 底层的程序语言是由 C 语言编写的,C 语言默认字符串则是以空字符结尾的字符数组(简称 C 字符串)。但 Redis 默认的字符串并非 C 字符串,而是名为 SDS ( Simple Dynamic String )简单动态字符串的抽象结构。

Redis 采用一段连续的内存空间来存储 SDS 结构,具体结构如下图所示,free 代表指这个字符串剩余可用空间的长度,而 len 代表这个字符串已占用空间的长度,buf [] 就是字符数组。

下面是一个字符串为 ‘Redis’ 在 SDS 结构中的存储例子:

在这里插入图片描述

图中 SDS 保留了 C 字符串以空字符结尾的传统,但这个空字符的长度1字节空间并不会计算在 SDS 的 len 属性里面,而是为这个空字符分配额外的1字节空间,每次由 SDS 相关函数默认添加到字符串末尾,Redis 这样设计的好处是 SDS 可以直接重用一部分 C 字符串相关函数。

这个 SDS 结构为字符串 ‘Redis’ 分配了5字节的已使用长度,也为其分配了5字节的可用空间长度。

SDS 与 C 字符串的区别

传统的 C 字符串长度为 n+1 且字符数组的最后一个元素总是空字符串 ‘\0’ 。

1.查找字符串长度的时间复杂度

假如查找字符串长度的时候,Redis 使用 C 字符串的话,因为 C 字符串不记录本身的长度,这样时间复杂度将为 O(N) ,而使用 SDS 查找字符串的长度的时候,直接获取 len 属性的值,这样的时间复杂度为 O(1) 。SDS 结构的设计极大的提升了 Redis 查找字符串长度时候的性能。

2.缓冲区溢出

使用 C 字符串存储一个字符串的时候,假如第一次为其分配的空间刚好被字符串占满空间了,第二次修改的时候,新的字符串长度已经超过其第一次分配的空间,然后执行修改的时候,此时超出的空间原本空间的内容将溢出到紧挨着的内容空间,导致下一个字符串的内容被修改。这样的缓冲区溢出是不友好的。

SDS 是不会发生缓冲区溢出的,它的空间分配策略拒绝了溢出。假如字符串发生了修改,SDS 相关函数会先检查本身的空间是否满足新的修改空间,如果不满足,函数将会自动扩容,再进行修改操作,所以 SDS 相对 C 字符串既不用手动修改空间满足正常存储,也不会发生缓冲区溢出。

3. 修改字符串时候的内存分配次数

因为 C 字符串的最后一个字符总是空字符串:’/0’ ,以及保存的时候长度总是 N+1, 这种关系下,字符串每次修改操作的时候,程序总要为其进行一次内存重新分配操作,修改的字符串长度变长的话,要为其重新分配合理的内存空间,不然可能发生缓冲区溢出。而变短的话,不分配回收无用的内存空间,可能将发生内存泄露。

基于这样的情况,Redis 这种是要求高性能的情况下,C 字符串的一次内存分配是没什么问题的,一但在频繁修改的情况下加上每次内存分配的耗时间,Redis 的性能是将极大降低的。

SDS 结构避免了以上这种缺点,采用了空间预分配以及惰性空间释放的方式。

空间预分配

当 SDS 结构的字符串变长的时候,SDS 会为其分配修改需要的空间以及未使用的空间。假如分配过同样的未使用的空间,下次字符串变长,需要增长的长度小于未使用空间,将直接使用未使用的空间,不需要执行内存分配。如果大于,则将会执行内存分配。

通过这样的策略,是能够减少字符串连续增长操作带来的内存空间分配次数的。

额外的空间分配规则是: 当长度( len 属性)小于 1MB 的时候,将分配同 len 属性一样大小的未使用空间。而当 len 属性大于 1MB 的时候,那么将分配 1MB 的未使用空间。

惰性空间释放

当 SDS 结构的字符串变短的时候,程序上不会立即内存分配,收回多余的空间,而是使用 free 属性把缩短的空间保存下来,以备后续使用。假如下次增长的字符串长度,free 属性值让其有足够的空间保存,那么下次不会发生内存分配,假如字符串继续变短的话,将继续保留多出的空间。SDS 相关函数可以在我们需要的时候,真正地释放SDS 的使用空间,不会造成内存浪费。

4. 二进制安全

由于 C 字符串要求保存的字符必须符合某种编码,并且除了字符串的最后一个字节为空字符之外,其他不能包含空字符。不然程序将认为第一个读到的空字符为字符串的末尾,之后的数据不会被识别。这样的局限性使得 C 字符串只能保存文本数据,而对平时常见的图片、音频等二进制数据不能保存。
而我们平时在使用 Redis 的时候,也需要存储这样的二进制数据,所以 Redis 使用 SDS 结构的好处就是由于 SDS 的相关函数都是二进制安全的,对于任何格式的数据都是以二进制保存到 buf 数组里面的,不会像 C 字符串那样用特殊的格式保存数据,让Redis 正常存储二进制数据,以及正常读取数据。

5. C 字符串函数兼容性

SDS 保留了 C 字符串以空字符结尾的传统,每次对 buf 数组分配空间的时候,会为其多分配一个字节的空间来保存这个末尾的空字符。为了让其能够兼容部分的 C 字符串函数。而 C 字符串是可以使用其全部的函数的。

通过兼容部分的函数,直接复用一些功能,而不用去重新编写函数实现某些特定的功能。

SDS 应用场景

Redis 中会使用 C 字符串应用在不需要对字符串值修改的地址,比如打印日志。SDS 是被 Redis 应用在需要修改字符串值的地方,比如 Redis 的数据库里面,包含字符串值的键值对在底层都是 SDS 实现的。

除了这些,SDS 还被应用于 AOF 模块中的 AOF 缓冲区以及客户端状态中的输入缓冲区。

参考:《 Redis设计与实现 》

更多Java后端开发相关技术,可以关注公众号「 红橙呀 」。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值