概述
Redis基于C语言实现,但Redis并没有采用C语言中传统的字符串表示,而是特别构建了一种叫做简单动态字符串(simple dynamic string)的数据结构,简称SDS,SDS是Redis中默认的字符串表示。
C字符串只在无须对字符串值修改的场景下作为字面量使用,如日志打印,而在字符串值可能被修改的场景下,均使用SDS表示字符串值。
SDS的实现代码均在sds.h和sds.c中。
本文使用的源码版本为Redis5.0,SDS的数据结构在Redis 3.2中有较大的改动,3.2版本以前的SDS数据结构定义相较简单,源码如下:
struct sdshdr {
unsigned int len; //表示buf[]数组中已使用的长度
unsigned int free; //表示buf[]数组中未使用的长度
char buf[]; //用于存储字符串的字节数组buf[]
};
本文只讨论新版本Redis的SDS数据结构、初始化和扩容等。
SDS数据结构
SDS结构源码
SDS的数据结构在sds.h文件内定义,总共定义了五种类型的SDS,源码如下:
// 五种SDS类型分别对应五种不同的初始长度 其中sdshdr5没有投入过使用
struct __attribute__((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__((__packed__)) sdshdr8 {
//buf数组中已被使用的长度
uint8_t len; /* used */
//buf数组的总长度
uint8_t alloc; /* excluding the header and null terminator */
// sds类型标识
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[];
};
数据结构属性说明
五种SDS类型大同小异,主要区别体现在有效位不同,SDS结构体定义总共有以下四个字段:
- buf[]:buf数组是一个字符数组,但在设计上被看作字节数组,后文会解释,用于保存字符串。
- len:表示buf数组中当前已被使用的长度。
- alloc:表示分配给buf数组的总长度,实际的值为buf[]的总长度减一,因为与C语言传统字符串一样,最后一个字节都保存了空字符’\0’,该位不实际保存字符串。
- flags:表示SDS是哪种类型的,因为只有五种类型的SDS,所以实际只有三位有效位,flag从sdshrd5到sdshdr64的取值分别为0~4。
其中uint8_t实际对应的数据类型就是unsigned char,表示当前SDS有八位有效位,uint16_t
对应的就是unsigned short,其他类型同理。
SDS结构图示
SDS的初始化流程
此部分展示代码均定义在sds.c文件中。
创建SDS时首先调用sdsnew()函数,sdsnew()函数源码如下:
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
函数的入参为初始化字符串,首先函数计算了传入字符串的长度,随后将初始字符串和该字符串长度作为参数传递,调用sdsnewlen()函数。
sdsnewlen()函数源码如下:
sds sdsnewlen(const void *init, size_t initlen) {
//首先定义了两个指针 其中sh指向整个sds的首地址 s指向buf[]的首地址
void