一:动态字符串
Redis构架了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,作为自己的默认字符串.除了保存数据之外,还可用作缓冲区:AOF模块中的AOF缓冲区,输入缓冲区。
SDS的定义
sruct sdshdr {
//记录buff数组中已使用的字节制度程度,不包括'\0',即字符串程度
int len;
//记录未使用字节数量
int free;
//字节数组
char buf[];
}
- 遵循C字符串空字符结尾惯例,'\0'的一字节空间不在计算范围内。
- 直接重用了一部分C字符串的函数,例如打印函数(printf)
SDS的特点
- C字符串:
- 使用长度为N+1的字符数组,以'\0'结尾,不能满足Redis对字符串安全性,效率以及功能方面的要求,字符串长度与底层字符数组空间相关联
- 本身不记录自身的长度,获取自身长度需要遍历整个字符数组,时间复杂度O(n)。
- 不记录自身长度还会导致容易导致缓冲区溢出
- 若在djccc后面插入字符串,会默认空间足够,导致覆盖别的字符串对象
- 内存重分配次数比较多,每次增长或缩短买都会进行一次内存重分配操作。
- 二进制不安全
- SDS:
- 常数复杂度获取字符串长度
- 因为结构体内有一个len属性记录了SDS的长度,每次字符串修改后都会自动修改len,这样可以O(1)复杂度获取字符串长度。
- 杜绝缓冲区溢出
- 结构体中有一个len和free属性,当需要修改字符串对象时,会让len与free相加,与要修改的字符串长度比较,若足够则在原本基础上修改,否者重新开辟一段内存空间,拓展对象空间。(包括空间预分配)
- 减少修改字符串时带来的内存重分配次数
- 增长或缩短都可能会导致内存重分配操作:
- 情景:
- 若增长后的长度大于底层字节数组空间,需要内存重分配
- 若缩短后不再使用那部分空间,由于没有修改数组,空间会一直占用,会产生内存泄漏
- 若一般情况下不太经常修改程度可以介绍,但Redis频繁修改,所以要减少内存空间重分配的次数。
- 有空间与分配和惰性空间释放两种策略。
- 空间预分配
- 拓展SDS空间之前会判断未使用空间(free)是否足够,若足够,则不需要拓展。
- 对SDS进行空间拓展的时候,不进会分配所修改必须要的空间,还会分配未使用的空间。
- 若SDS修改后的长度(即len)将小于1MB,那么将分配与len等长的空间,即len=free,若大于等于1MB,会分配1MB未使用空间,即free=1MB。
- 惰性空间释放
- 缩短空间时,不会立即通过内存重分配回收未使用空间,而是会使用free来记录下来,以便在以后增长的时候利用,减少增长时导致的内存重分配,以及本次缩短导致的内存重分配。
- 通时,SDS提供了相应的API,可以在有需要的时候释放SDS未使用的·空间,不需要担心惰性空间释放策略造成的内存浪费。
- 另外可能会导致内存碎片问题,当SDS的未使用空间散布在SDS字符串的各个位置时,可能会出现无法分配连续内存块的情况,从而导致内存碎片。为了解决这个问题,Redis提供了内存碎片整理(memory defragmentation)功能,可以将SDS字符串的内容移动到连续的内存块中,从而消除内存碎片。但是,内存碎片整理需要消耗额外的时间和资源,因此需要在必要时才进行。
- 情景:
- 增长或缩短都可能会导致内存重分配操作:
- 二进制安全
- buf是字节数组而不是字符数组,是二进制安全的。C的字符串字符数组必须符合某种二进制编码,只能保存文本文件。
- 不是用这个数组来保存字符的,而是来保存一系列二进制数据,所以保存'\0'等一些特殊字符都没关系,因为他是通过len而不是'\0'来判读是否结束的,字节数组也可以保存一些特殊的数据如音频,视频等,不会对其中的数据做任何限制、过滤、或者假设,数据写入是什么样的,读取就是什么样的。
- 兼容部分C字符串函数
- 他在结尾用'\0'是为了保存文本数据的那些SDS可以重用一部分<string.h>库定义的函数,例如对比函数(<string.h>/strcasecmp)、追加函数(将SD作为地个人股参数追加到C字符串后面。
- 常数复杂度获取字符串长度