Redis中SDS简单动态字符串

文章目录

  • 前言
  • 一、SDS结构
  • 二、SDS与C字符串的区别
    • 常数复杂度获取字符串长度
    • 杜绝缓冲区溢出
    • 减少修改字符串是带来的内存分配次数
    • 空间预分配
    • 惰性空间释放
    • 二进制安全


前言

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


一、SDS结构

SDS的定义如下:

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

下图展示了SDS示例:
Redis中SDS简单动态字符串_第1张图片

free 属性的值为0,表示这个SDS没有分配任何未使用空间。
len属性的值为5,表示这个SDS保存了一个五字节长的字符串。
buf属性是一个char类型的数组,数组的前5个字节分别保存了‘R’,‘e’,’’d,’i’,’s’五个字符,而最后一个字节则保存了空字符‘\0
SDS遵循C字符串以空字符串结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的1字节空间,以及添加空间字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符对于SDS的使用者来说是完全透明的。遵循空字符结尾这一惯例的好处是,SDS可以直接重用一部分C字符串函数库里面的函数。

二、SDS与C字符串的区别

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

因为C字符串不记录自身的长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,对遇到的每个字符进行计数,直到遇到代表字符串结尾的空字符为止,这个操作的复杂度未O(N)。在SDS中记录了SDS本身的长度,所以获取一个SDS长度的复杂读仅为O(1).

杜绝缓冲区溢出

C字符串在做字符串内容拼接的时候,需要先分配足够的空间,否则会发生缓冲区溢出。比如下stract函数可以将src字符串的内容拼接到dest字符串的末尾:
char *stract(char *dest,const char *src);

假设程序里有两个在内存中紧邻着的C字符串s1和s2,其中s1保存了字符串“MongoDB”,如图所示
Redis中SDS简单动态字符串_第2张图片
执行如下操作:
strcat(s1,”Cluster”);

在执行这个操作之前,没有给s1分配足够的空间,那么在stract函数之后,s1的数据将溢出到s2所在的空间中,导致s2保存的内容被意外的修改,如下:
Redis中SDS简单动态字符串_第3张图片
与C字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用SDS既不需要手动修改SDS空间大小,也不会出现前面所说的缓冲区溢出问题。

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

在C字符串中每次对其执行增长或缩短操作,程序总要对保存这个C字符串的数组进行一次内存重分配操作。因为内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时的操作。对性能也会产生一定的影响。

SDS中有一个free属性记录未使用空间,通过该属性实现了空间的预分配和惰性空间释放两种优化策略。

空间预分配

如果对SDS修改后,SDS长度(也即是len属性的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDS len属性的值将和free属性的值相同。例如如果修改之后,SDS的len将变成13字节,那么程序也会分配13字节的未使用空间,SDS的buf数组实际长度将变成13+13+1=27字节(额外的一字节用于保存空字符)。

如果对SDS进行修改之后,SDS的长度将大于等于1MB,那么程序会分配1MB的未使用空间。例如如果修改之后,SDS的len将变成30MB,那么程序会分配1MB的未使用空间,SDS的buf数组的实际场所为30MB+1MB+1Byte。

通过空间预分配策略,Redis可以减少连续执行字符串增长所需要的内存分配次数。因为在扩展SDS空间之前,SDS API会先检查未使用空间是否足够,如果足够的话,API就会直接使用未使用空间,而无需执行内存重分配。

惰性空间释放

惰性空间释放用于优化SDS字符串的缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等到将来使用。
与此同时,SDS也提供了相应的API,让我们在有需要时,真正地释放SDS的未使用空间,所以不用担心惰性空间释放测试会照成内存浪费。

二进制安全

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

SDS不会对数据做任何限制,数据在写入时是什么样的,它被读取的时候就是什么样。

你可能感兴趣的:(Java,redis,数据库,缓存,SDS)