redis字符串剖析
说明
本篇文章介绍redis使用的字符串结构。redis很巧妙的在操作的指针前面增加header,并且会在尾部增加’\0’,兼容二进制的同时,也可以直接将指针传递给C库操作字符串的函数直接操作
实现
sds定义
简单的重定义了char*,即能兼容C库操作字符串的函数,也能跟原来的字符串有一定区分
1 | typedef char *sds; |
sds header
为了节约内存,sds根据实际存储的字符串长度,使用多个header进行描述
1 | struct __attribute__ ((__packed__)) sdshdr5 { |
- sdshdr*后面的数字即为该header能存放的字符串长度二进制位数,例如sdshdr8表示该结构体能容纳2^8-1长度的字符串
- 所有header结构体,存放buf的前面一位肯定是flags,该变量存放当前header属于什么类型,使用了
__attribute__ ((__packed__))
属性告诉编译器不要对齐,这样就可以使用s[-1]来简单的拿到flags,再根据flasgs,来获取header指针进行操作 - 除了sdshdr5以外,其他header结构相似,len表示实际字符串长度,alloc表示实际分配的长度
- sdshdr5只有一个存放len的变量,所以该结构不能预先分配内存
- redis提供助手宏或者函数,可以更方便的操作sds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 获取header指针
// 获取字符串指针
// 用于计算SDS_TYPE_5的实际长度
// 根据类型获取字符串长度
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}
// 返回字符串剩余未使用的空间
static inline size_t sdsavail(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5: {
return 0;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
return sh->alloc - sh->len;
}
}
return 0;
}
其他函数
知道了redis字符串结构之后,它提供的一系列操作它的函数就很容易看懂。包括一些初始化,复制,清空,free,增长,格式化等等。这里给出每个函数的声明及作用,具体细节可以查看我注释的redis源码
1 | sds sdsnewlen(const void *init, size_t initlen); // 根据传入的init指针,和initlen创建一个合适的sds,为了兼容c字符串函数,sds总是会以'\0'结尾 |