Redis~字符串的数据结构之动态字符串(SDS)

动态字符串(SDS)

  • Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字 符串),而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示

  • 当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis 就会使用SDS来表示字符串值,比如在Redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的。举个例子,如果客户端执行下面的命令,那么Redis将在数据库中创建一个新的键值对,其中:

  • 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串“msg”的SDS

  • 键值对的值也是一个字符串对象,对象的底层实现是一个保存着字符串“hello world”的 SDS

  • 当做缓冲区使用:除了用来保存数据库中的字符串值之外,SDS还被用作缓冲区(buffer)。AOF模块中的 AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的

SDS的底层实现

struct sdshdr {
    //记录buf数组中已使用字节的数量,等于SDS所保存字符串的长度
    int len;
 
    //记录buf 数组中未使用字节的数量
    int free;
 
    //字节数组,用于保存字符串
    char buf[];
};
  • SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符对于SDS的使用者来说是完全透明的。遵循空字符结尾这一惯例的好处是,SDS可以直接重用一部分C字符串函数库里面的函数

为什么不直接使用C字符串

效率差异
  • C字符串的主要缺陷就是因为它没有记录自己的长度,而如果在需要了解长度时,就只能通过O(N)的效率进行一次遍历
    -
  • 和C字符串不同,因为SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度仅为O(1)

在这里插入图片描述

缓冲区溢出
  • 并且因为C字符串没有统计剩余空间的字段,也没有容量字段,所以很容易就会因为strcat等函数造成缓冲区的溢出,为弥补这一缺陷,redis在sds中增加了free字段

  • 为解决C字符串缓冲区溢出问题以及长度计算问题,SDS中引入了len来统计当前已使用空间长度,free来计算剩余的空间长度

  • 通过标记剩余空间,当对SDS进行插入操作时,就会提前判断当前剩余空间是否足够,如果不足则会先进行空间的拓展,再进行插入,这样就解决了缓冲区溢出的问题

空间预分配策略与惰性空间释放策略
  • C语言的不足之处:C语言在处理字符串时,如果字符串需要拼接或者释放,那么都会进行内存的重分配,然后将新字符串的值保存在新的内存中。

  • 但是Redis作为数据库,经常被用于速度要求严苛、数据被频繁修改的场合,如果每次 修改字符串的长度都需要执行一次内存重分配的话,那么光是执行内存重分配的时间就会占 去修改字符串所用时间的一大部分,如果这种修改频繁地发生的话,可能还会对性能造成影响

  • 为了提高内存分配的效率,防止大量使用内存重分配而调用系统函数导致的性能损失问题(用户态和内核态的切换),Redis主要依靠空间预分配和惰性空间释放来解决这个问题

  • 空间预分配: 为减少空间分配的次数,当需要进行空间拓展时,不仅仅会为SDS分配修改所必须要的空间,并且会为SDS预分配额外的未使用空间。主要策略如下

  1. 当SDS修改后的长度小于1MB时,将会预分配大小和当前len一样的空间(free = len),也就是使空间增长一倍,来减少因为初始时申请大空间导致的连续分配问题
  2. 当SDS修改后的长度大于等于1MB时,每次分配都会分配1MB的空间,防止空间的浪费。
  • 惰性空间释放: 当我们对SDS进行删除操作时,并不会立即回收删除后空余的空间,而是将空余空间以free字段记录下来,以备后面使用。
  • 这样做的目的在于防止因为空间缩短后因为再度插入导致的空间拓展问题。
  • 并且如果有需求需要真正释放空间,Redis也提供了对应的API,所以不必担心会因为惰性的空间释放而导致的内存浪费问题。
二进制安全
  • C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制 使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据

  • SDS的API都是二进制安全的 (binary -safe),所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数 据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,它被 读取时就是什么样

  • 这也是我们将SDS的buf属性称为字节数组的原因——Redis不是用这个数组来保存字符, 而是用它来保存一系列二进制数据

  • 使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用len属性 的值而不是空字符来判断字符串是否结束

兼容部分C字符串函数
  • 由于SDS底层基于C字符串,以空字符结尾, 所以他是兼容部分C字符串函数, 从而避免了不必要的代码重复

总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值