前言
redis中的SDS(dynamic strings library)是在Redis中的一种基本的数据结构,这里将对sds进行解析。
子类型
typedef char *sds;
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; //实际使用的长度
uint8_t alloc; //长度,可以理解为容量
unsigned char flags; //低三位表示类型
char buf[];
};
sdshdr16、sdshdr32、sdshdr64和sdshdr8都类似,这里不多介绍,sdshdr5也根本没使用,这里重点介绍sdshdr8,首先在结构体关键字struct后面有__attribute__ ((packed)),这个是禁止内存对齐,节约空间,在结构体成员后面有一个char buf[],这里是柔性数组,sdshdr8的大小仅为3,buf[]并不占用内存。紧接着,有以下宏定义:
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
类型从0到4,flags中的低三位完全可以表示,下面就是获取类型的方法:
struct sdshdr8 s8;
s8.flags & 0x07 // s8.flags & SDS_TYPE_MASK
但可能会有一个疑问,我明知道它的类型,我再获取它的类型不是多此一举吗,这个问题先放这里,待会儿再说。
子类型的应用
定义了这么多子类型,肯定是为不同长度的字符串选择最合适的类型,不然就是浪费空间,因此就来了sdsnew函数:
sds sdsnew(const char *init) { // sds就是char *
size_t initlen = (init == NULL) ? 0 : strlen(init);//计算出来字符串的长度
return sdsnewlen(init, initlen);
}
在sdsnewlen函数中:
sds sdsnewlen(const void *init, size_t initlen) {
return _sdsnewlen(init, initlen, 0);
}
_sdsnewlen主要实现在这里:
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
void *sh;//定义泛型指针
sds s;//定一个字符串指针
char type = sdsReqType(initlen);//根据字符串的初始长度选择一个最合适的类型,就是我们上面提到的SDS_TYPE_16这些,这里实现比较简单,不多叙述
//最低也是使用SDS_TYPE_8,放弃 SDS_TYPE_5
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);//根据计算的类型返回各个子类型结构体的大小
unsigned char *fp; /* flags pointer. */
size_t usable;
//和内存分配的时候一样,判断是否溢出
assert(initlen + hdrlen + 1 > initlen);
//根据trymalloc的值选择分配内存的函数,重新分配内存大小是:初始长度+最合适的子类型结构体大小+1,这个1是为‘\0’准备
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &usable) :
s_malloc_usable(hdrlen+initlen+1, &usable);
if (sh == NULL) return NULL;
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);//初始化,全部赋0
//这步骤把sh向前移动hdrlen个字节,直接指向的是字符串的内容
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;//这是指向flags的指针
usable = usable-hdrlen-1;//可用的内存大小
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
//内存已经分配好加初始化,现在开始把相关信息写入
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
//又是一个宏
//执行之后的效果是struct sdshdr8 *sh = (void*)((s)-(sizeof(struct sdshdr8)));
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
}
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}
注意:
- 此时返回的s依然是char *,也就是sds,但是s指针之前的hdrlen长度已经有信息写入
- 分配内存的时候会根据字符串的长度自动分配
- 虽然结构体内有记录字符串长度的信息,但是字符串仍然是以’\0’结束
sdsfree
有分配就有释放,释放的函数sdsfree为:
void sdsfree(sds s) {
if (s == NULL) return;
s_free((char*)s-sdsHdrSize(s[-1]));
}
s[-1]是sds中的flag,sdsHdrSize(s[-1])获取的是结构体的大小,(char*)s-sdsHdrSize(s[-1])才是实际申请的内存首地址,s_free就是z_free
其他的函数
sdsupdatelen
void sdsupdatelen(sds s) {
size_t reallen = strlen(s);
sdssetlen(s, reallen);
}
这个函数时为了让我们直接对s进行操作时实时更新其实际长度。
sdsMakeRoomFor
如果我们想对一个字符串进行扩展,就需要开辟更大的空间:
sds sdsMakeRoomFor(sds s, size_t addlen) {
return _sdsMakeRoomFor(s, addlen, 1);
}
具体实现的函数是_sdsMakeRoomFor
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
void *sh, *newsh;
size_t avail = sdsavail(s);//看看还有多少空间可用
size_t len, newlen, reqlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
size_t usable;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;//剩余空间够用
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
reqlen = newlen = (len+addlen);
assert(newlen > len); /* Catch size_t overflow */
if (greedy == 1) {//为1的时候表示自动增添空间
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
}
type = sdsReqType(newlen);
/* Don't use type 5: the user is appending to the string and type 5 is
* not able to remember empty space, so sdsMakeRoomFor() must be called
* at every appending operation. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
if (oldtype==type) {
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);//这个其实就是z_realloc_usable
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
usable = usable-hdrlen-1;
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
sdssetalloc(s, usable);
return s;
}
sdsResize
这里的resize调整的是alloc,也就是容量
sds sdsResize(sds s, size_t size) {
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);
sh = (char*)s-oldhdrlen;
/* Return ASAP if the size is already good. */
if (sdsalloc(s) == size) return s;
/* Truncate len if needed. */
if (size < len) len = size;
/* Check what would be the minimum SDS header that is just good enough to
* fit this string. */
type = sdsReqType(size);
/* Don't use type 5, it is not good for strings that are resized. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
/* If the type is the same, or can hold the size in it with low overhead
* (larger than SDS_TYPE_8), we just realloc(), letting the allocator
* to do the copy only if really needed. Otherwise if the change is
* huge, we manually reallocate the string to use the different header
* type. */
if (oldtype==type || (type < oldtype && type > SDS_TYPE_8)) {
newsh = s_realloc(sh, oldhdrlen+size+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+oldhdrlen;
} else {
newsh = s_malloc(hdrlen+size+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
}
s[len] = 0;
sdssetlen(s, len);
sdssetalloc(s, size);
return s;
}