redis 5.0.7 源码阅读——动态字符串sds

redis中动态字符串sds相关的文件为:sds.h与sds.c

一、数据结构

redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构

 1 typedef char *sds;
 2 
 3 /* Note: sdshdr5 is never used, we just access the flags byte directly.
 4  * However is here to document the layout of type 5 SDS strings. */
 5 struct __attribute__ ((__packed__)) sdshdr5 {
 6     unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
 7     char buf[];
 8 };
 9 struct __attribute__ ((__packed__)) sdshdr8 {
10     uint8_t len; /* used */
11     uint8_t alloc; /* excluding the header and null terminator */
12     unsigned char flags; /* 3 lsb of type, 5 unused bits */
13     char buf[];
14 };
15 struct __attribute__ ((__packed__)) sdshdr16 {
16     uint16_t len; /* used */
17     uint16_t alloc; /* excluding the header and null terminator */
18     unsigned char flags; /* 3 lsb of type, 5 unused bits */
19     char buf[];
20 };
21 struct __attribute__ ((__packed__)) sdshdr32 {
22     uint32_t len; /* used */
23     uint32_t alloc; /* excluding the header and null terminator */
24     unsigned char flags; /* 3 lsb of type, 5 unused bits */
25     char buf[];
26 };
27 struct __attribute__ ((__packed__)) sdshdr64 {
28     uint64_t len; /* used */
29     uint64_t alloc; /* excluding the header and null terminator */
30     unsigned char flags; /* 3 lsb of type, 5 unused bits */
31     char buf[];
32 };

定义结构体时,加上了 __attribute__ ((__packed__)) 关键字,用于取消字节对齐,使其按照紧凑排列的方式,占用内存。这样做的目的并不仅仅只是为了节约内存的使用。结构体最后有一个 char buf[],查了资料之后了解到,其只是定义一个数组符号,并没有任何成员,不占用结构体的内存空间,其真实地址紧随结构体之后,可实现变长结构体。由此可以只根据sds字符串的真实地址,取到sds结构体的任意成员变量数据。如flags:

1 void func(const sds s)
2 {
3     unsigned char flags = s[-1];      
4 }

这个flags,从源码的注释上看,其低三位用于表示sds类型,高五位是当sds类型为sdshdr5时,表明字符串长度的。对于非sdshdr5的类型,有专门的成员变量描述当前已使用的长度,及总buffer长度。

sds类型:

1 #define SDS_TYPE_5  0
2 #define SDS_TYPE_8  1
3 #define SDS_TYPE_16 2
4 #define SDS_TYPE_32 3
5 #define SDS_TYPE_64 4

sds定义了两个宏,用于获取sds结构体首地址:

1 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
2 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

由此可见sds结构体的大致结构为:

 1 /*
 2 sdshdr5
 3 +--------+----...---+
 4 |00011000|abc      |
 5 +--------+----...---+
 6 |flags   |buf
 7 
 8 sdshdr8
 9 +--------+--------+--------+----...---+
10 |00000011|00000011|00000001|abc      |
11 +--------+--------+--------+----...---+
12 |len     |alloc   |flags   |buf
13 */

sds的一些常规操作,如获取字符串长度、获取剩余buf长度等,都是其于以上操作,首先根据sds字符串地址获取其flags的值,根据flags低三位判断sds类型,接着使用宏SDS_HDR_VAR或SDS_HDR进行操作。如:

 1 #define SDS_TYPE_MASK 7   //0000,0111
 2 
 3 static inline size_t sdslen(const sds s) {
 4 //获取flags
 5     unsigned char flags = s[-1];
 6 //根据flags低三位取类型,根据类型做不同处理
 7     switch(flags&SDS_TYPE_MASK) {
 8         case SDS_TYPE_5:
 9             return SDS_TYPE_5_LEN(flags);
10         case SDS_TYPE_8:
11             return SDS_HDR(8,s)->len;
12         case SDS_TYPE_16:
13             return SDS_HDR(16,s)->len;
14         case SDS_TYPE_32:
15             return SDS_HDR(32,s)->len;
16         case SDS_TYPE_64:
17             return SDS_HDR(64,s)->len;
18     }
19     return 0;
20 }

关于sds结构体中的len与alloc,len表示的是sds字符串的当前长度,alloc表示的是buf的总长度。

二、一些操作。

首先是一个根据字符串长度来决定sds类型的方法

 1 static inline char sdsReqType(size_t string_size) {
 2     if (string_size < 1<<5)    //flags高五位最大数字为 1<<5 - 1
 3         return SDS_TYPE_5;
 4     if (string_size < 1<<8)    //uint8_t 最大数字为 1<<8 - 1
 5         return SDS_TYPE_8;
 6     if (string_size < 1<<16)  //uint16_t 最大数字为 1<<16 - 1
 7         return SDS_TYPE_16;
 8 #if (LONG_MAX == LLONG_MAX)  //区分32位/64位系统
 9     if (string_size < 1ll<<32)
10         return SDS_TYPE_32;
11     return SDS_TYPE_64;
12 #else
13     return SDS_TYPE_32;
14 #endif
15 }

创建一个新的sds结构体:

 1 sds sdsnewlen(const void *init, size_t initlen) {
 2     void *sh;
 3     sds s;
 4     char type = sdsReqType(initlen);
 5     if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
 6     int hdrlen = sdsHdrSize(type);
 7     unsigned char *fp; /* flags pointer. */
 8 
 9     sh = s_malloc(hdrlen+initlen+1);
10     if (init==SDS_NOINIT)
11         init = NULL;
12     else if (!init)
13         memset(sh, 0, hdrlen+initlen+1);
14     if (sh == NULL) return NULL;
15     s = (char*)sh+hdrlen;
16     fp = ((unsigned char*)s)-1;
17     switch(type) {
18         case SDS_TYPE_5: {
19             *fp = type | (initlen << SDS_TYPE_BITS);
20             break;
21         }
22         case SDS_TYPE_8: {
23             SDS_HDR_VAR(8,s);
24             sh->len = initlen;
25             sh->alloc = initlen;
26             *fp = type;
27             break;
28         }
29         case SDS_TYPE_16: {
30             //同SDS_TYPE_8,略
31         }
32         case SDS_TYPE_32: {
33             //同SDS_TYPE_8,略
34         }
35         case SDS_TYPE_64: {
36             //同SDS_TYPE_8,略
37         }
38     }
39     if (initlen && init)
40         memcpy(s, init, initlen);
41     s[initlen] = '