简单动态字符串
Redis只在一些无需对字符串进行修改的地方使用C字符串,大部分时候使用简单动态字符串(simple dynamic string, SDS),字符串的抽象类型。二进制安全,可以存放任意格式的数据。
定义
1 源码(部分): 2 struct __attribute__ ((__packed__)) sdshdr5 { 3 unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 4 char buf[]; 5 }; 6 struct __attribute__ ((__packed__)) sdshdr8 { 7 uint8_t len; /* used */ 8 uint8_t alloc; /* excluding the header and null terminator */ 9 unsigned char flags; /* 3 lsb of type, 5 unused bits */ 10 char buf[]; 11 }; 12 书中代码: 13 struct sdshdr { 14 int len; 15 int free; 16 char buf[]; 17 }
这里以书中代码为基础,len为使用的字节数,free表示已分配但未使用的字节数。buf就是char型数组保存字符串。
这里需要注意的是SDS保存的字符串沿用C字符串的格式,会在最末尾加一个'\0'表示字符串结束,好处是可以复用C语言的函数库。
举个例子:“hello” len为5,如果free为0,那么这个字符串一共使用6字节,'\0'额外使用一个字节。
由定义我们可以了解到,SDS的长度可以O(1)获得,而且他使用的内存不是长度+1。而且具有二进制安全的性质。
空间分配策略
Redis为了减少内存分配的次数,采用了空间预分配和惰性空间释放两种策略。
空间预分配
当需要对SDS进行空间扩展的时候,SDS不仅会分配修改所必须的空间,还会进行预分配。
如果修改后的len值小于1M,那么就会预分配和len同样大小的预留空间。也就是说经过修改和预分配len和free的值是相等的。这时候占有的字节数就是len+free+1。
而如果修改后的len值大于等于1M,那么就只预分配1M空间。
惰性空间释放
当API需要缩短SDS保存的字符串,并不会立即释放多出来的字节,而是使用free记录下来。这样避免了内存重分配的操作,并为将来可能的增长提供了优化。
如果确实需要释放未使用的空间,SDS也提供了相应的API。
优点总结
- 常数时间复杂度获取字符串长度
- 杜绝缓冲区溢出
- 减少在修改字符串长度时内存分配的次数
- 二进制安全
- 兼容部分C字符串函数