Redis内置数据结构之简单动态字符串

REDIS 二月 21, 2020

Redis内置数据结构之简单动态字符串

文章字数 3.4k 阅读约需 3 mins. 阅读次数 1000000

简单动态字符串(simple dynamic string,以下简称SDS)是Redis的一个内置数据结构。Redis默认字符串并不是C字符串,而是SDS,Redis只会使用C字符串作为字面量用在一些无需更改字符串值的地方。

作用

  1. 是Redis的默认字符串表示
  2. 保存数据库中的字符串值
  3. 用作AOF缓冲区以及客户端状态中的输入缓冲区

定义

SDS定义在sds.h中,以结构体的形式定义,每个sdshdr结构体表示一个SDS值:

1
2
3
4
5
6
7
8
struct sdshdr{
// 记录buf数组中已使用字节的数量,等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};

下面给出一个保存了一个5字节长度的字符串、空闲空间为0的SDS示例:

图1 SDS示例

SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在len属性里,为空字符分配空间、添加空字符到字符串末尾等操作都由SDS函数自动完成,因此这个空字符对于SDS使用者来说可谓完全透明。遵循惯例是因为SDS可以直接重用一部分C字符串函数(比如printf)。

SDS与C字符串的区别

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

C字符串不记录自身长度,在C语言中为获取字符串长度需要遍历整个字符串直到遇到‘\0’,复杂度O(N)。对于SDS来说,只需访问len属性便可获知长度,复杂度O(N),提升了Redis的性能。

杜绝缓冲区溢出

同样由于C字符串不记录自身长度,容易因为不内置检查机制使得在操作字符串时忘记考虑空间是否足够,往往导致缓冲区溢出,比如strcat()函数就具有这个隐患。而当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足需求,不满足则自动拓展其空间才执行修改操作,因而避免缓冲区溢出问题,比如sdscat()函数。

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

C字符串在进行修改操作时,为避免缓冲区溢出或者内存泄漏,往往需要进行复杂的内存重分配,SDS由于具有free空间和len属性,使其可以通过某些优化策略来减少重分配内存的次数,从而提高性能。

  1. 空间预分配:

    用于优化SDS的字符串增长操作:SDS API先检查free是否足够,若不够,不仅分配所必须要的空间,还会分配额外的未使用空间。

    • if 修改后的SDS长度<1MB then 分配的free = 修改后的len
    • else then 分配的free=1MB

    (总空间=free+len+1byte(空字符))

  2. 惰性空间释放

    用于优化SDS字符串缩短操作:在需要对SDS进行缩短操作时,SDS API并不直接释放空闲出来的空间,而是将其归入free中,以备后面可能发生的字符串增长操作。

    同时,SDS会提供API使得在需要的时候释放未使用空间,因此不用担心惰性空间释放策略会造成内存浪费。

二进制安全

C字符串的字符必须符合某种编码(如ASCII),并且除字符串末尾外不能包含空字符,否则会提前结束字符串的读取,这些限制使得C字符串只能保存文本数据,无法保存图片、音频等二进制数据。

所有SDS API都会以处理二进制的方式处理SDS存放在buf数组里的数据,数据原封不动进行存取,所以被称为字节数组。这也是为什么要用len存放长度的原因,在判断字符串结束时也用len而不是空字符。

这个机制使得Redis可以保存多种场景的数据。

兼容部分C字符串函数

SDS遵循C字符串以空字符结尾的惯例,使得SDS可以重用C语言的字符串函数库里的部分函数,避免重复写相同功能的SDS函数。

0%