Redis设计与实现(持续更新)(1)简单动态字符串

一、什么是简单动态字符串(SDS)

 

struct sdshdr{
//记录buf组数已经使用的字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组未使用字节的数量
int free;
//字节数组,用来存放字符串
char buf[];
}

redis没有使用c中的传统string,而是自己创建了一个SDS,并且将他作为redis的默认字符串。

单看还是看不懂,我们来举例子,这里我直接拿redis设计与实现这本书的画的图进行讲述

 顾名思义,我的buf是存在字符串的,为什么是末尾是‘\0'是因为,他也需要标记结束字符,遇到'\0'就默认字符串结束,这里的好处是SDS可以复用c字符串中函数中的一些函数。这里如果不懂,可以去看看c的string类型。

这里我们举个例子我们可以复用printf()函数

printf("%s", s->buf);

len:指的是buf里面目前存放着5字节长的字符串

free:我剩下还有多少字节可以进行分配空间

然后再贴一张还有剩下空间的SDS图

想必你也了解他们代表的意思的吧,那恭喜你redis设计与实现开始入门了

接下来我们就要进阶了解了哦,准备好了没有!

二、SDS与c字符串string的区别

说了SDS的结构,我们上文也提及到了redis选择自己构建sds作为默认字符串,让我们为什么要吃力不讨的构建新的类型呢?

 1.优点常量级遍历

c语言的string遍历

c的字符串遍历为O(n)

而SDS遍历只需要访问len的长度

即O(1)常量级

2.杜绝缓冲区溢出

c字符串在拼接的

 我使用

strcat(s1, " Cluster");

则你会发现我s1的内容溢出到s2的位置了,则就出现了缓冲区的溢出

而SDS就杜绝的缓冲区溢出的原因,原因是什么呢?

当SDSApi,需要对SDS进行修改,他会坚持SDS的空间是否满足修改条件,如果不满足的画,就api会自动将sds的空间扩展至修改所需的大小,然后才会实际的修改操作。

执行上图一样的连接及操作后

他们是这么分配多的空间呢?别急,接下来就马上讲

3.减少修改字符串时带来的内存重分配次数

 因为 C 字符串并不记录自身的长度, 所以对于一个包含了 N 个字符的 C 字符串来说, 这个 C 字符串的底层实现总是一个 N+1 个字符长的数组(额外的一个字符空间用于保存空字符)。

因为 C 字符串的长度和底层数组的长度之间存在着这种关联性, 所以每次增长或者缩短一个 C 字符串, 程序都总要对保存这个 C 字符串的数组进行一次内存重分配操作:

  • 如果程序执行的是增长字符串的操作, 比如拼接操作(append), 那么在执行这个操作之前, 程序需要先通过内存重分配来扩展底层数组的空间大小 —— 如果忘了这一步就会产生缓冲区溢出。
  • 如果程序执行的是缩短字符串的操作, 比如截断操作(trim), 那么在执行这个操作之后, 程序需要通过内存重分配来释放字符串不再使用的那部分空间 —— 如果忘了这一步就会产生内存泄漏。

 但是由于redis一般都被要求低延迟,高读写,高必发,这种是效率很低的所以才有sds的空间预分配和惰性空间释放两种优化策略。

空间预分配:

空间预分配用于优化 SDS 的字符串增长操作: 当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。

其中, 额外分配的未使用空间数量由以下公式决定:

  • 如果对 SDS 进行修改之后, SDS 的长度(也即是 len 属性的值)将小于 1 MB , 那么程序分配和 len 属性同样大小的未使用空间, 这时 SDS len 属性的值将和 free 属性的值相同。 举个例子, 如果进行修改之后, SDS 的 len 将变成 13 字节, 那么程序也会分配 13 字节的未使用空间, SDS 的 buf 数组的实际长度将变成 13 + 13 + 1 = 27 字节(额外的一字节用于保存空字符)。
  • 如果对 SDS 进行修改之后, SDS 的长度将大于等于 1 MB , 那么程序会分配 1 MB 的未使用空间。 举个例子, 如果进行修改之后, SDS 的 len 将变成 30 MB , 那么程序会分配 1 MB 的未使用空间, SDS 的 buf 数组的实际长度将为 30 MB + 1 MB + 1 byte 。

通过空间预分配策略, Redis 可以减少连续执行字符串增长操作所需的内存重分配次数。

 我们继续拿上面那个图为例子哦

 

首先我没有超过1m,那么就再需要的基础上再多开一一次,我有13个字节的字符串,然后我free也会多开13个字节的空间,留着空闲

懒惰空间释放

 

: 当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。

举个例子, sdstrim 函数接受一个 SDS 和一个 C 字符串作为参数, 从 SDS 左右两端分别移除所有在 C 字符串中出现过的字符

sdstrim(s, "XY");   // 移除 SDS 字符串中的所有 'X' 和 'Y'

 

 注意执行 sdstrim 之后的 SDS 并没有释放多出来的 8 字节空间, 而是将这 8 字节空间作为未使用空间保留在了 SDS 里面, 如果将来要对 SDS 进行增长操作的话, 这些未使用空间就可能会派上用场。

举个例子, 如果现在对 s 执行:

sdscat(s, " Redis");

 二进制安全

 兼容部分c字符串函数

 上文有提及到SDS也有'\0\m,则他也可以复用c字符串的部分函数

总结、

c字符串SDS
获取字符串长度的时间复杂度为O(n),一个一个的遍历为常量级O(1),访问len即可
api,不安全,会造成缓冲区溢出安全,会自动分配空间
修改字符串长度 N 次必然需要执行 N 次内存重分配修改字符串长度 N 次最多需要执行 n次内存重分配
只可以存储文本数据可以保存文本和二进制
使用所有string库的函数使用string部分函数

这个是重点,牢固记

SDS的api

 如果看的满意,请你动动你的手指点点赞,评论,你的关注是我莫大的支持,我也会继续加油的!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值