简单动态字符串
简介
简单动态字符串(Simple Dynamic String),简称SDS;用作 Redis 的默认字符串表示。
SDS 的定义
struct sdshdr
{
// 记录 buf数组中已使用的字节的数量
// 等于 SD所保存的字符串的长度
int len;
// 记录 buf数组中未使用的字节的数量
int free;
// 字节数组,不是用于保存字符,而是用它来保存二进制数据(往下看:二进制安全)
char buf[];
};
示例:redis 中存入一个 Redis 的字符串
备注下:SDS 遵循C语言传统字符串表示(简称C字符串)以空字符串结尾的惯例,但是保存空字符串的1字节空间不计算在SDS 的len属性中,并且为空字符串分配额外的1的字节空间,对使用者来说空字符串是透明的;遵循空字符串的惯例是为了重用部分C字符串的函数库中的函数
SDS 与C 字符串的区别
假设 redis 中存储了一个Redis 的字符串
常数复杂度获取字符串长度
C 字符串并不记录自身的长度信息,所以在获取C 字符串的长度时候程序必须便利整个字符串,对每个字符进行计数,直到遇到字符串结尾的空字符串位置,复杂度为O(N)。
SDS 中有个属性字段 len,可直接返回字符串长度,复杂度为O(1)。对于 len 字段的更新,由SDS 底层的API 在执行更新操作的时候自动完成的。
避免缓冲区溢出
C 字符串并不记录自身的长度带来的另一个问题就是容易导致缓冲区溢出;C 字符串在添加或者更新数据的时候没有额外申请之前刚好的内存就会导致内存溢出。
SDS 在需要进行修改的时候,会优先检查SDS的空间是否满足修改所需的要求,若不满足则自动将SDS的空间扩展到执行修改的所需的大小然后再执行修改操作。
减少修改字符串时带来的内存重分配次数
C 字符串并不记录自身的长度,所以C 字符串的长度和底层数组的长度之间存在着关联,每次增长或缩短字符都要进行内存重新分配;若是增长字符串,那么程序需要优先内存重分配来扩展底层数组的空间的大小,不然就是导致缓冲区溢出;若是缩短字符串的操作,那么在执行这个操作之后通过内存重分配来释放字符串不再使用那部分空间,不然导致内存泄漏
SDS 避免C 字符串的问题,采用了 预分配 和 惰性空间释放 两种策略:
预分配
SDS 对字符串新增并且需要对SDS空间进行扩展的时候,SDS 底层的API 不仅会分配新的所需空间,并且会分配额外的未使用空间(对SDS 进行修改后,len的长度小于1MB,则API 会给free分配和len 一样的空间大小;若 SDS 长度大于等于 1MB,那么API 会分配1MB 的未使用空间)
惰性空间
SDS 对字符串进行缩短的时候,API并不立即宗信分配多端后的字节空间,二十记录到free 属性中;再有需要的时候进行内存释放
二进制安全
C 字符串除了字符串末尾之外不能包含空字符,否则就会被程序误以为是字符串结尾,从而导致了C字符串只能存储文本
SDS 的API都是二进制安全的,都会以处理二进制的方式来处理SDS存放在buf 数组里面的数据,程序不会对其中的数据做任何的限制、过滤或者假设;这就是 SDS 的属性 buf 是字节数组的原因。
兼容部分C 字符串函数
这里不说明了