简单动态字符串
在Redis中的字符串表现形式并没有使用C的字符串形式,在这里的C字符串的表现是形式是以空字符结尾的字符串数组,而是使用一个叫做简单字符串(SDS)的抽象类型,在Redis源码中代码如下:
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
使用这个结构体的原因主要是在C语言中,如果用字符串数组代表字符串的话,首先肯定无法直观的知道字符串数组的长度,每次都需要遍历字符串数组来获取长度,而且字符串的话我们肯定还会进行字符串拼接和分割的操作,这样子就会涉及到空间的释放和分配,这不但会耗时,而且如果分配或者释放不当的话就容易造成内存泄漏或者缓冲区溢出的风险,所以使用上面的结构体是很有必要的。
在这个结构体中len代表字符串数组的已使用字节的数量,free代表未使用字节的数量,buf字符串数组用来保存字符串,这样子实现的话数组的长度获取就由O(n)将到了O(1),并且也不会出现缓冲区溢出或者内存泄漏的情况。
并且为了优化SDS的字符串增长操作,Redis采用了空间预分配的策略,就是说,如果如果SDS的API对一个SDS进行修改比如字符串拼接时,它不但会为SDS分配拼接后所必需的空间,还会为SDS分配额外的未使用的空间。其中额外的未使用空间分配方案有如下两种:
- 如果对SDS进行修改后,SDS的长度如果小于1MB,那么分配等于SDS长度的为使用空间;
- 如果对SDS进行修改后,如果SDS的长度大于等于1MB,那么就分配1MB的未使用空间,
例如SDS的len为30MB,那么未使用空间的长度就分配1MB,所以SDS的buf数组长度就为30MB+1MB+1byte。
通过这种策略,SDS连续增长N次字符串的所需重分配次数从必定n次降低为最多N次。
为了优化SDS的字符串缩短操作,提出了惰性空间释放的方案,该方案实现如下: - 当SDS的API要缩短SDS保存的字符串的时候,程序并不会立刻释放缩短的字节数,会先把这些字节数的数量纪录到free中,并且等待未来使用。比如说SDS的长度为30字节,然后SDS的API要对SDS释放8字节,他会清空数组后八个字节地址的数据,然后free+8,等到我们对SDS的字符串进行拼接5个字节的操作时,我们就不需要执行内存重分配,free中的8字节够它玩的了。
还有一点使用这个结构体的好处就是,如果我们使用C的字符数组来保存字符串时,只允许字符串的结尾处可以有空字符,而不允许字符串的中间有空字符,否则在读取长度时就会被截断丢失数据,在这个结构体,并不是由结尾空字符来读取字符串长度,而是使用len来读取字符串长度,这样子就不会丢失数据。