Redis数据结构--简单动态字符串(SDS)

276 阅读4分钟

目录

  • 前言
  • SDS定义
  • SDS较之C字符串优点
  • SDS API

前言

近来工作之余仍有一些空闲时间,想着巩固学习一些知识,刚好最近工作使用redis较多,知识更多的是停留在(单机)应用层面上。于是在业余时间学习了《redis设计与实现》第二版。通过此书,我了解到更多关于redis底层的原理,明白了redis设计巧妙之处。更加坚定了我对redis深入学习的决心,记录笔记以加深印象。


SDS定义

SDS:简单动态字符串,它区别于 C 字符串实现原理,是由redis自己构建的字符串结构,并且是redis默认的字符串数据结构。

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

buf数组结尾以'\0',表示空字符,遵循C字符串以'\0'结尾管理,得以保存C字符部分特性。


SDS较之C字符串优点

从SDS结构可以清除看出,SDS较之C语言有点,特别之处就在于lenfree 属性的使用。

优点如下:

  • 常数复杂度获取字符串长度
  • 杜绝缓冲区溢出
  • 减少字符串修改带来的内存重分配次数
  • 二进制安全
  • 兼容部分C字符串函数

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

C语言中获取字符串长度,需要遍历字符数组,复杂度为O(N),而Redis中记录了字符串长度,获取长度操作复杂度为O(1)
假如有个场景需要频繁调用STRLEN(获取字符串长度)命令,此时不管字符串长度多长,我们只需要去获取len属性值即可,复杂度永远为1。从而避免了对系统性能的影响。

杜绝缓冲区溢出

C语言对字符串的部分修改操作存在缓冲区溢出的问题,关键就在于修改缓冲区前,未对缓冲区大小进行校验是否合法。所以redis中 SDS API在修改字符串之前对缓冲区大小进行了校验,避免出现缓冲区溢出问题。

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

C语言中,存储字符串的字符数组是固定的(N+1),每次修改字符串长度时,都需要对数组进行重分配(内存分配),此操作为耗时操作。
在字符串修改次数不那么频繁的场景中,这种内存分配可能会被接受,但是如果redis作为数据库时,这种频繁操作数据库的行为则不会被接受。
所以为了减少频繁调用,SDS采用了未使用空间(free属性)的方法。

通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略:
a、空间预分配:空间预分配用于优化SDS的字符串增长操作;当SDS API对一个SDS进行修改,并且需要对SDS进行空间扩展时(否则扩展时直接使用未使用空间),程序不仅会为SDS分配所必须的空间,还会为其分配额外未使用空间。
  分配公式:
   如果对SDS修改后,len长度小于1MB,那么程序分配和len属性同样大小未使用空间。这时len=free,举个例子:SDS修改后len变成13字节,那么程序也会分配13字节的未使用空间。SDS的buf实际长度将变为13+13+1=27字节(额外1字节用于保存空字符)。
   如果对SDS修改后,len长度大于1MB,那么程序分配1MB的额外未使用空间。举个例子,SDS修改后len变为30MB,那么程序会分配额外未使用空间1MB,SDS的buf实际长度将变为30MB+1MB+1byte
b、惰性空间释放:惰性空间释放用于字符串缩短操作;当SDS的API需要缩短SDS保存的字符串时,程序不会立即使用内存重分配来回收缩短后多出来的字节,而是用free属性将这些字节记录下来,等待将来使用

二进制安全

C字符串中,字符数组中首个'\0'字符即是字符串结尾。这种特性对于可能带有多个'\0'字符的二进制数据流来说是不安全的。造成C语言字符串不能存储文本数据流。而SDS没有沿用C语言特性,保证了能够存取二进制数据流。(存图片等本文二进制数据)

兼容部分C字符串函数

区别:


SDS API