sds是redis中简单的字符串实现
sdsnewlen()方法可以用来申请sds。
sds sdsnewlen(const void *init, size_t initlen);
第一个参数是sds中字符串的内容,initlen则是第一次初始化的长度。
首先会根据第一次初始化所需要的长度根据其所占位数通过sdsReqType()得到内存的结构体。
static inline char sdsReqType(size_t string_size) {
if (string_size < 1<<5)
return SDS_TYPE_5;
if (string_size < 1<<8)
return SDS_TYPE_8;
if (string_size < 1<<16)
return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
if (string_size < 1ll<<32)
return SDS_TYPE_32;
return SDS_TYPE_64;
#else
return SDS_TYPE_32;
#endif
}
但是,如果一开始申请的是一个初试长度为0的空字符串,那么并不是按照最小的5位容量,还是为了方便这类空字符串的后续添加,直接申请8位容量。
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
在确定了所容纳的位数,则需要根据相应的位数通过sdsHdrSize()方法确定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; /* 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[];
};
除了5位的之外,其他相应位数容量的头构体都有一个相应的位数的大小len来表示当前的sds中字符串的大小,alloc代表当前申请的总大小。char数组为存储内容的存储空间,flag只用低三位表示类型,高五位并没有使用。
sh = s_malloc(hdrlen+initlen+1);
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
这个时候申请参数中所需要的长度与sds头结构的大小之和再加1的内存空间,多出来的那一位存放’/0’代表sds的结尾。内存空间起始内存为止加头长度就是具体存储空间也就是char数组的起始位置。存储空间前一位则是第三位代表sds类型的flag字段。
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;
接下来很简单,将相应的头数据写入头部的内存空间,把字符串数据写入相应的char数组,最后在最后一位写入结束位’/0’,代表sds的申请结束。
sdsMakeRoomFor()方法则可以对已有的sds进行扩容。
sds sdsMakeRoomFor(sds s, size_t addlen);
如果需要增加的长度小于当前sds剩余可用的容量,则并不需要再继续申请新的容量。剩余可用的容量则通过头部的alloc-len则可以得到。
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;
之后如果确定需要新增相应的长度,那么则需要确定具体要申请内存到多大。
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen);
新扩容的长度为新增加的长度与原长度之和的二倍,但是如果之和本身已经大于1024*1024,则增加1024*1024。最后根据新扩容的长度重新确定存储的结构体。
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1);
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(hdrlen+newlen+1);
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);
}
sdssetalloc(s, newlen);
return s;
如果原本为5位,那么直接扩容为8位。
如果扩容前后的结构体一致,直接存储头的大小则不用发生变化,否则将会通过malloc()彻底重新申请新的内存空间。