Redis02:底层:字符串
底层:字符串
在redis中除了少数用来打印日志信息的字符串,其他字符串都是以SDS的形式存储的,SDS的全称是simple dynamic string。
SDS的定义
SDS的定义如下:
struct sdshdr{
//SDS保存的字符串的长度
int len;
//buf数组中未使用的字节数量
int free;
//用于保存字符串的字节数组,可能存在一部分未使用空间
char buf[];
} sdshdr;
下图就是一个存有字符串Redis的SDS:
free值是5代表数组中有5个字节未被使用,数组中的字符串以空字符\0结尾,len值代表字符串的真正长度,不包括空字符。存在空字符的好处在于,这样处理后的SDS在底层和c字符串几乎一样,所以可以直接重用一部分C字符串函数库里面的函数。
与C字符串的对比
常数复杂度获取字符串长度
C字符串不记录长度信息,所以为了获取字符串长度就必须遍历整个字符串,这个操作的复杂度为ON,而SDS可以根据len字段立即获得字符串长度,这确保了获取字符串长度不会成为redis的性能瓶颈。
杜绝缓冲区溢出
C字符串不记录自身长度可能会导致缓冲区溢出的情况出现,比如在没有考虑分配空间的情况下对字符串进行拼接操作,新生成的字符串可能很大,大到覆盖了周围的有用数据。而SDS在修改时,首先会检查SDS的空间是否满足修改的要求,如果不满足的话会进行空间扩展,不会有溢出问题出现。
内存分配策略
C的字符串每次改变时,都会对保存字符串的数组进行一次内存重分配操作,如果是增长字符串就是重分配来扩展空间,这个过程可能产生缓冲区溢出;如果是缩短字符串就是重分配来缩小空间,如果不释放空间就会产生内存泄露。
内存重分配涉及系统调用,是一个比较耗时的操作,在一般的程序中,不频繁的内存重分配是可以接受的,但是在redis需要的速度比较严苛,为了克服这种缺陷,SDS通过free这个字段来降低内存分配的次数,主要从两个方面改善:
1、空间预分配:每次增加字符串长度的时候,SDS不会分配一个正好大小的字符数组,而是分配的稍大一点,以便未来的增长操作不会触发内存重分配。
如果修改SDS后,SDS的len小于1MB,那么此时SDS会分配与len相同的空闲空间,如修改后SDS的len是13字节,那么free也是13字节,整个SDS的buf数组实际长度是13+13+1=27字节。
如果修改SDS后,SDS的len大于等于1MB,那么程序会分配1MB的未使用空间。
2、惰性空间释放:当SDS执行缩短操作时,程序不立即使用内存重分配来回收缩短的字节,而是使用free将这些字节的数量记录下来,以备将来使用。SDS也有对应的API来释放真正的未使用空间,不用担心造成空间浪费。
二进制安全
C字符串中的字符必须符合某种编码,除了字符串的末尾外字符串内部不能有空字符,这使得C字符串只能保存文本数据。而redis中的SDS是二进制安全的,可以保存非文本数据,它保存的是一系列二进制数据,SDS通过检查len来判断字符串是否结束,所以没有空字符的限制。