在 Redis 中,字符串都用自定义的结构简单动态字符串(Simple Dynamic Strings,SDS)。
Redis 中使用到的字符串都是用 SDS,例如 key、string 类型的值、sorted set 的 member、hash 的 field 等等等等。。。
数据结构
旧版本的结构
在 3.2
版本之前,sds 的定义是这样的:
struct sdshdr {
// buf 数组中已使用的字节数量,也就是 sds 本身的字符串长度
unsigned int len;
// buf 数组中未使用的字节数量
unsigned int free;
// 字节数组,用于保存字符串
char buf[];
};
这样的结构有几个好处:
- 单独记录长度
len
,获取字符串长度的时间复杂度是 O ( 1 ) O(1) O(1) 。传统的 C 字符串获取长度需要遍历字符串,直到遇到\0
,时间复杂度是 O ( N ) O(N) O(N)。 - buf 数组末尾遵循 C 字符串以
\0
结尾的惯例,可以兼容 C 处理字符串的函数。 - 减少修改字符串带来的内存重分配次数,Redis 使用了 空间预分配(预先申请大一点点的空间) 和 空间惰性释放(字符串变短修改
len
字段即可)来减少字符串修改引起的内存重新分配。 - 不以
\0
为结尾的判断,二进制安全。因为图片等二进制数据中,可能包含\0
,传统 C 字符串一遇到\0
就认为字符串结束了,会导致不能完整保存。
缺点:
len
和free
的定义用了 4 个字节,可以表示2^32
的长度。但是我们实际使用的字符串,往往没有那么长。4 个字节造成了浪费。
新版本的结构
旧版本中我们说到,len
和 free
的缺点是用了太长的变量,新版本解决了这个问题。
我们来看一下新版本的 SDS
结构。
在 Redis 3.2 版本之后,Redis 将 SDS 划分为 5 种类型:
类型 | 字节 | 位 |
---|---|---|
sdshdr5 | < 1 | <8 |
sdshdr8 | 1 | 8 |
sdshdr16 | 2 | 16 |
sdshdr32 | 4 | 32 |
sdshdr64 | 8 | 64 |
新版本新增加了一个 flags
字段来标识类型,长度 1 字节(8 位)。
类型只占用了前 3 位。在 sdshdr5
中,后 5 位用来保存字符串的长度。其他类型后 5 位没有用。
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 前 3 位保存类型,后 5 位保存字符串长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 字符串长度,1 字节 8 位 */
uint8_t alloc; /* 申请的总长度,1 字节 8 位 */
unsigned char flags; /* 前 3 位保存类型,后 5 位未使用 */
char buf[]