共计 1831 个字符,预计需要花费 5 分钟才能阅读完成。
Redis 没有间接应用 C 语言的字符串,而是本人构建了一个名为简略动静字符串 (simple synamic string) 的形象类型,并将 SDS 作为 Redis 默认的字符串示意。当 Redis 存储的不仅仅是一个字符串面量,而逝一个能够被批改的字符串的时候,就会用到 SDS。例如:应用 SET msg “hello world” 存储,底层是通过 SDS 实现的。
一、SDS 的定义
SDS 构造体:
struct sdshdr {
int len;
int free;
char buf[];}
上边的构造体是 SDS 的定义,蕴含三个属性信息,len 示意的是字符串的长度,在存储的时候间接计算好的,free 示意 SDS 除了存储的字符串之外,额定预留的空间,buf[]用于存储字符串的值,须要留神的是:在存储的时候除了存储字符串自身的值之外,还会在值的结尾存储一个空字符 ’\O’,这一点遵循了 C 字符串以空字符结尾的常规,这么做的益处是,SDS 能够间接重用 C 字符串函数库中的函数。
SDS 与 C 字符串的区别
(一)、查问长度的复杂度
C 字符串应用长度为 N + 1 的字符数组来示意长度为 N 的字符串,在字符串的结尾应用一个空字符完结。因为 C 语言在存储字符串的时候并不间接计算字符串的长度,所以获取字符串长度须要应用遍从来实现,工夫复杂度为 O(n);SDS 在存储字符串值的时候间接计算并写入 len,要获取字符串长度间接查问 len 即可,实现复杂度为 O(1),Redis 通过应用 SDS,将查问字符串的长度的工夫复杂度从 O(n)升高到了 O(1)。
(二)、杜绝缓冲区溢出
因为 C 字符串在存储的时候不计算字符串长度,所以 strcat 假如用户执行这个函数时曾经为 dest 调配了足够多的内存空间,能够存储所有的字符串值,然而如果 strcat 的字符串长度大于调配的缓存空间,就会造成一部分字符串存进去了,另一部分因为没有空间存储不了,造成缓冲区的溢出、数据不平安。
在 Redis 中应用 SDS,SDS 构造中有一个属性 free,该属性为 SDS 存储字符串后又额定预留了一部分存储的空间。在须要会已存入的字符串追加值的时候会先查看 free 的大小,如果 free 大于要追加的字符串长度,则间接执行追加操作。反之,API 会先将 SDS 的 free 批改至追加值的长度后,再执行追加操作。扩大 free 的操作不须要手动,Redis 会主动实现。
(三)、缩小批改字符串时的内存重调配次数
在 C 字符串操作的时候,如果是拼接操作,须要先执行内存重调配来扩大底层数组空间的大小,而后执行拼接操作,如果遗记了内存充沛配会造成内存溢出。如果执行截断操作,须要在执行截断后,执行内存重调配来开释掉不在应用的内存空间,如果遗记了后一步操作就会造成内存透露。
在 Redis 中,SDS 通过未应用空间解除了字符串长度和底层数组长度的关联:buf 的长度不肯定是存储的字符串长度加 1,数组里蕴含着未应用的字节,这些字节的数量由 free 来记录。SDS 解决 C 字符串问题提供了如下两个策略:
- 空间预调配
空间预调配用于优化字符串增长的操作,当 SDS 的 API 须要对一个 SDS 批改,并且须要进行空间扩大的时候,程序不仅会为 SDS 调配须要应用的空间,还会为 SDS 调配额定的未应用的空间。如果批改之后 SDS 的长度 len 小于 1M,程序会为 SDS 调配与 len 属性等值的额定空间;如果 SDS 批改之后长度 len 大于 1M,程序会为 SDS 调配 1M 的额定内存空间。 通过空间预调配策略,Redis 缩小了间断执行字符串增长带来的内存重调配次数,将 C 的 N 次升高到最多 N 次。 - 惰性空间开释
惰性空间开释用于优化字符串的缩短操作:当 SDS 的 API 须要缩短保留的字符串时,在执行缩短操作之后并不会间接开释因为缩短操作带来的多余空间,而是将这些空间长度记录到 free 中,留待未来应用,这样既缩小了内存重调配的次数,也为未来有可能增长的操作提供了优化。SDS 还提供了 API,在有须要的时候真正的开释 SDS 未应用的空间,这样就不会造成内存的节约。
(四)、二进制平安
C 字符串中的字符必须合乎某种编码,并且除了字符串开端的空字符之外,字符串里不能蕴含空字符,否则会被程序误读为结尾,这些限度造成 C 字符串只能保留文本数据,而不能保留图片、音频、视频等二进制数据。
在 Redis 中为了适应各种存储的场景,SDS 的 API 都是二进制平安的(binary-safe),所有须要存储的数据都会被 API 以二进制的形式解决后在存储到 buf 中,不会对存储的数据类型做限度,所以是二进制平安的。