Redis实现:简单动态字符串 SDS

介绍

Redis没有使用C字符串(以空字符结尾),而是自己构建了简单动态字符串(Simple Dynamic String,SDS)的抽象类型,并将SDS用作Redis的默认字符串支持

用处

  • 数据库中的字符串值(键值对的键、字符串值、其他类型的字符串部分)
  • 缓冲区,AOF模块的AOF缓冲区,客户端状态的输入缓冲区

定义

// sds.h
struct sdsdr{
    int len;
    int free;
    char buf[];
}
  • len:字符串长度
  • free:未使用空间
  • buf:保存字符的数组,字节数组,后面会提到

字符串长度不包括结尾空字符,因此buf的长度应该为len+free+1

sds遵循的c字符串空字符结尾的好处是可以直接宠用c字符串函数库里的函数

SDS与C字符串的区别

1. 常数复杂度获取字符串长度

因为字符串的长度直接由len记录(o(1)),所以不必遍历获取字符串长度(o(n)),更新字符串长度的工作由SDS的API在执行时自动完成。

2. 杜绝缓冲区溢出

因为C字符串不记录自身长度,所以容易造成缓冲区溢出

比如由一段内存连续排着两个字符串(‘H’,‘i’,’\0’,‘W’,‘o’,‘r’,‘l’,‘d’,’\0’)

此时将Hi改成Hello则会变成(‘H’,‘e’,‘l’,‘l’,‘o’,‘r’,‘l’,‘d’,’\0’)

而SDS的API在对SDS进行修改的时候,会先检查SDS空间是否满足需求,不满足则SDS会自动扩展,再执行修改操作

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

由于C字符串不保存自身长度,N个字符的字符串由N+1长度的字符数组实现(有一个结尾空字符),字符串长度和数组长度关联。每次增长或缩短字符串,都需要进行一次内存重分配,不然就会缓冲区溢出(增长)或者内存泄漏(缩短)。而且内存重分配涉及复杂算法,并且可能需要执行系统调用,所以比较耗时(Redis作为数据库对速度要求严苛,数据频繁修改)。

SDS通过未使用空间解除了字符串长度和数组长度的关联。SDS通过未使用空间实现了空间预分配惰性空间释放两种优化策略

空间预分配

空间预分配用于优化SDS的字符串增长操作,当SDS需要扩展空间的时候,不仅分配修改所需的空间,也分配额外未使用空间,而分配额外未使用空间有两种公式

  • 如果修改之后,len<1MB,则分配和len相同的未使用空间,len将和free的值相同。假设“Redis”字符串len=5,free=0 ,数组长度为6,在Redis后添加“ Cluster”(前面有空格),会给两部分都分配13字节的空间,len将会更新为13,而free也会相应更新为13,数组长度为13+13+1=27
  • 如果修改之后,len>=1MB,那么程序则会分配1MB的未使用空间

惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作,当SDS的API需要缩短SDS保存的字符串时,程序并不会立即使用内存重分配来回收缩短后多出来的字节,而是更新free属性,将这些字节数量记录起来,等待将来使用

通过惰性空间释放,SDS避免了缩短字符串所需的内存重分配操作,并未将来可能的增长操作提供优化

当然,SDS也提供了相应的API,让我们可以在有需要的时候,真正地释放SDS未使用空间,所以不必担心内存浪费

4. 二进制安全

c字符串必须符合某种编码(如ASCII),并且在字符串之间不能有空字符,不然会认为在这里结束,这样子,c字符串就只能保存文本数据,不能保存二进制数据。SDS的API都会以处理二进制的方式处理SDS存放在buf数组里,数据写入什么样,读取就什么样。

所以说SDS的buf是字节数组,redis用它来存二进制数据。(虽然sds使用了空字符串,但它是用len来判断字符串结束)

5. 兼容部分C字符串函数

sds可以用来保存二进制数据,但它也遵循了空字符的惯例,所以当它保存文本数据的时候,可以使用一部分C字符串的函数.

通过以空字符串结尾的惯例, SDS可以在有需要时重用<string.h>函数库,避免不必要的代码重复

6. 总结

C字符串SDS
获取字符串长度时间复杂度o(N)获取字符串长度时间复杂度o(1)
API是不安全的,可能造成缓冲区溢出API安全,不会造成缓冲区溢出
修改字符串长度n次必然需要执行n次内存重分配修改字符串长度n次,最多n次内存重分配
只能保存文本数据可以保存文本数据和二进制数据
可以使用全部<string.h>库的函数可以使用部分<string.h>库的函数

SDS API

函数作用时间复杂度
sdsnew创建给定C字符串的SDSo(N), N是C字符串长度
sdsempty创建空SDSo(1)
sdsfree释放给定的SDSo(N),N是SDS长度
sdslen返回SDS的已使用空间字节数o(1)
sdsavail返回SDS未使用空间字节数o(1)
sdsdup创建一个给定SDS的副本o(N),N是SDS长度
sdsclear清空SDS保存的字符串内容o(1)
sdscat将C字符串拼接到SDS后面o(N), N是C字符串长度
sdscatsds将SDS拼接到SDS后面o(N),N是被拼接SDS长度
sdscpy将给定C字符串赋值到SDS里面,覆盖原来的字符串o(N), N是C字符串长度
sdsgrowzero用空字符串将SDS扩展至给定长度o(N),N是扩展新增长度
sdsrange保留SDS给定区间内的数据,不在区间内的数据会被覆盖或清除o(N),N是被保留数据长度
sdstrim接受一个SDS和一个C字符串,将SDS中C字符串出现过的字符移除o(N2), N是C字符串长度
sdscmp对比两个SDS是否相等o(N),N是两个SDS中较短的长度

参考《Redis设计与实现》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值