《Redis设计与实现》笔记——简单动态字符串(SDS)

前言

Redis中构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型。
作用:

  • 表示字符串值
  • 用作缓冲区:AOF模块中AOF缓冲区,客户端状态中的输入缓冲区

示例:
如果客户端执行命令:

redis > SET msg "hello world"
OK

那么Redis将在数据库中创建一个新的键值对,其中:
键值对的键是一个字符串对象,底层是一个保存着字符串 "msg" 的SDS
键值对的值也是一个字符串,底层是一个保存着字符串 "hello world" 的SDS

SDS定义

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

在这里插入图片描述
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节,空间不计算在SDS的len属性里,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作都是由SDS函数自动完成的。
好处:遵循C字符串以空字符结尾的好处是可以重用一部分C字符串函数库里的函数

SDS与C字符串的区别

(1) 提升获取字符串长度性能

  • C字符串不记录自身的长度,获取C字符串长度时必须遍历整个字符串,时间复杂度为O(N)
  • 而SDS在len属性中记录了SDS本身的长度,获取一个SDS长度的时间复杂度为O(1)

(2)杜绝缓冲区溢出
下面是一个字符串拼接函数

char *strcat(char *dest,const char *src);
  • 由于C字符串不记录自身的长度,strcat假定用户在执行这个函数时,已经为dest分配足够多的内存,可以容纳src字符串中的所有内容,如果这一假设不成立,那么就会造成缓冲区溢出

示例:
假设程序里有2个在内存中紧邻的C字符串s1="Redis"s2="MongoDB",如果执行strcat(s1," Cluster")
之前忘记给s1分配足够多的空间,那么在执行strcat函数后,s1的数据将溢出到s2所在的空间,导致s2保存的内容被意外的修改了,如下图所示:
在这里插入图片描述

  • 当SDS API中有一个用于拼接操作的sdscat函数,在需要对SDS进行修改时,API会先检查SDS的空间十分足够,如果不空间不够的话,会自动将SDS空间扩展至执行修改所需的大小,然后再执行实际的修改操作,所以使用SDS不需要手动修改SDS空间大小,也就不会出现缓冲区溢出问题

示例:
执行sdscat(s," Cluster"),我们看到sdscat不仅对这个SDS进行了拼接操作,还为SDS分配了13字节的未使用空间,并且拼接之后的字符串正好是13字节长
在这里插入图片描述

(3)减少内存重分配次数

  • 每次增长或缩短一个C字符串时,需要对这个C字符串数组进行一次内存重分配操作

    • 增长字符串操作,比如拼接操作(append),在这个操作之前如果忘记内存重分配,会造成缓冲区溢出
    • 缩短字符串操作,比如截断操作(trim),在这个操作之后如果忘记内存重分配来释放不再使用的空间,会造成内存泄露
  • SDS实现了空间预分配和惰性空间释放两种优化策略

    • 空间预分配:当修改SDS时,会对SDS进行扩容
      扩容策略:(1)修改后SDS长度小于1MB,额外分配和len大小相同的未使用空间(加倍扩容);(2)修改后长度大于等于1MB,额外分配1MB的未使用空间

    注意:字符串最大长度512MB

    • 惰性空间释放:当缩短SDS保存的字符串后,程序不会立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用
    • SDS 提供了对应的API,真正的释放SDS未使用的空间,不用担心惰性释放策略会造成内存浪费。

(4)二进制安全

  • C字符串中的字符必须符合某种编码(比如ASCII),且除了字符串末尾外,字符串里面不能包含空字符,否则最新被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据。
  • SDS的API都是二进制安全的,所有的API都会以二进制的方式来处理SDS存放在buf数组里的数据,通过使用二进制安全的SDS,使得Redis不仅可保存文本数据,还可以保存任意格式的二进制数据

(5)兼容部分C字符串函数
由于SDS字符串遵循C字符串以空字符结尾的管理,来支持复用部分C字符串函数

总结

区别项C字符串SDS
获取字符串长度时间复杂度O(N)O(1)
缓冲区溢出问题API不安全,可能造成缓冲区溢出API安全,不会造成缓冲区溢出
内存重分配问题修改字符串长度,必然需要执行内存重分配修改字符串长度,不一定需要执行内存重分配
存储数据类型只能保存文本数据可以保存文本或二进制数据
C字符串函数使用可以使用所有的可以使用一部分

参考文献

  • 《Redis设计与实现(第二版)》-by 黄健宏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值