Redis源码分析:数据结构之简单动态字符串(SDS)
本节的分析基于redis-6.0.0
内容在 src/sds.h 和 src/sds.c 中
C语言中的字符串是以空字符(\0)结尾的字符数组,被称为 C 字符串,如果字符串中间混入了空字符,就会产生截断的后果,这就是非二进制安全。而且。C字符串无法自动扩容。Redis实现了一个可以自动扩容且二进制安全的字符串,叫做简单动态字符串(simple dynamic string),简称 SDS。SDS在Redis中广泛使用,意义重大。在不需要修改字符串值的地方,仍在继续使用C字符串。
SDS的结构
早先的SDS结构的定义很直观,如下:
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
注意,保存空字符的1字节空间不计算在 SDS 的 len 属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由 SDS 函数自动完成的。
buf 是个柔性数组,只能放在结构体的末尾。
redis-6.0.0中的设计是这样的:对于不同长度的字符串,Redis底层使用了不同的SDS结构体。
sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,定义为:
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
len:表示 buf中已占用字节数。
alloc:表示buf中已分配字节数,不同于free,记录的是为buf分配的总长度。
flags:标识当前结构体的类型,低3位用作标识位,高5位预留。
buf:柔性数组,存储数据的地方。
通过对buf指针减一,能方便定位到flags。
相关API的实现
*sds sdsnewlen(const void init, size_t initlen) ;
创建SDS
实现方式:
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */
sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL;
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}