Redis数据结构_2.简单动态字符串SDS

Redis数据结构_简单动态字符串SDS

Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用。这个对象系统包括字符串对象,哈希对象,列表对象,集合对象,有序集合对象等。但是Redis面向内存并没有直接使用这些对象。而是使用了简单动态字符串,链表,字典(散列表),跳跃表,整数集合,压缩列表这些数据结构来操作内存。

简单动态字符串SDS

redis的底层使用C语言实现的,但在字符串方面确没有使用以空字符结尾的字符数组的C字符串,而是特别构建了一种叫简单动态字符串(simple dynamic string, SDS)的自定义类型,并将其作为了redis的默认字符串来使用。

struct sdshdr{

    //int 记录buf数组中已使用字节的数量即sds的长度
    int len;

    //int 记录buf数组中未使用字节的数量 
    int free;

    //字符数组用于保存字符串 sds遵循了c字符串以空字符结尾的惯例目的是为了重用c字符串函数库里的函数
    char buf[];
}

在redis数据库中包含字符串的键值对都是由SDS来实现的,例如:

在客户端执行命令:

set msg “hello world”

那么reids将创建两个SDS,来分别保存"msg"和"hello world"。

执行命令:

RPUSH fruits “apple” “banana” “cherry”

那么redis将创建一个SDS来保存键值fruits,创建3个SDS来分别保存列表对象的三个值

为什么要使用SDS

在这里插入图片描述

上图表示了SDS与C字符串的区别,关于为什么Redis要使用SDS而不是C字符串,我们可以从以下几个方面来分析。

1. 杜绝缓冲区溢出

C字符串在未正确分配内存时容易造成溢出,例如:

内存中保存着紧邻的两个C字符串s1、s2,其中s1保存"Redis",s2保存"Mongo DB":
在这里插入图片描述

如果这时程序执行:

strcat(s1,“Cluster”);

将s1的内容修改为"Redis Cluster",但如果之前确没有为s1重新分配足够的空间,那么s1的数据将会溢出到s2的空间中:
在这里插入图片描述

与C字符串不同SDS的的空间分配策略会完全杜绝这种情况的发生,SDS在修改字符串的值前会检查空间是否符合要求, 如果不满足, API会自动对它动态扩展, 然后再进行修改。

2.减少内存重分配次数

每次增长或缩短一个C字符串,程序都要对保存这个字符串的的数组进行一次内存重分配操作:

  • 增长字符串:比如拼接操作(append),执行操作前需要重新分配内存来扩展底层数组的大小,如果忘了这一步就会产生缓冲区溢出

  • 缩短字符串:比如截断操作(trim),执行操作前需要重新分配内存来释放不在使用的空间,如果忘了这一步就会产生内存泄露

通过记录未使用空间大小(free),SDS实现了空间预分配和惰性空间释放两种优化策略

空间预分配

空间预分配用于优化SDS的字符串增长操作,当需要对SDS的空间进行扩展时,程序不仅会为SDS分配所必须的空间,还会为SDS分配额外的未使用空间,额外未使用空间的大小遵循一下公式:

  • 如果对SDS进行修改后,SDS的长度(既len属性的大小)小于1 MB,那么程序将会为SDS分配与修改后长度相同的空间,比如修改后SDS的长度为13bytes(既len = 13bytes),那么程序将会给SDS分配13bytes的未使用空间(既free = 13bytes),而此时SDS的大小为 13 bytes + 13 bytes +1 bytes (额外1 bytes用于保存空字符)

  • 如果对SDS进行修改后,SDS的长度(既len属性的大小)大于等于与1 MB,那么程序将会为SDS分配1 MB空间,比如修改后SDS的长度为30 MB(既len = 30 MB),那么程序将会给SDS分配1 MB的未使用空间(既free = 1 MB),而此时SDS的大小为 30 MB + 1 MB +1 bytes

惰性空间释放

空间预分配用于优化SDS的字符串缩短操作,当对SDS所保存的字符串进行缩短时,程序并不会立即回收多出来的空间,而是使用free属性来记录空间大小等待将来使用,Redis SDS API提供相应的API让我们在有需要的时候真正的释放SDS的未使用空间。

3. 二进制安全

C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

如果有一种使用空字符来分割多个单词的特殊数据格式,就不能用C字符串来表示,如”Redis\0String”,C字符串的函数会把’\0’当做结束符来处理,而忽略到后面的”String”。而SDS的buf字节数组不是在保存字符,而是一系列二进制数组,SDS API都会以二进制的方式来处理buf数组里的数据,使用len属性的值而不是空字符

4.常数级时间复杂度

Redis主要操作API及时间复杂度
在这里插入图片描述
在这里插入图片描述

可以看到几乎都是常数级别

总结

  • redis只会使用C字符串作为字面量,大多数情况下使用SDS作为字符串

  • 与C字符串相比SDS具有以下有点:

    • 杜绝缓冲区溢出
    • 减少修改字符串时所需的内存重分配次数
    • 二进制安全
    • 兼容部分C字符串函数
    • 各种常用操作的常数级时间复杂度

参考:

《Redis设计与实现》

https://www.laoyu.site/2018/技术实践/redis/Redis数据结构——简单动态字符串SDS/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值