sds,全称 Simple Dynamic Strings,是 Redis 自定义的一个字符串类型。
typedef char *sds;
看到这你必定心田感觉 Redis 在逗你,这不就是一个字符数组么,怎么就 Simple Dynamic Strings 了呢 ! 没错,我过后也是这么感觉的,然而仔细阅读源码后发现 sds 并不是一个人在战斗,它还有战友 sdshdr,sdshdr 是个五胞胎,别离是 sdshdr5,sdshdr8,sdshd16,sdshdr32,sdshd64。块头从小到大。
sdshdr 全称 Simple Dynamic Strings Header
/* 因为生的跟他人不一样(内部结构不一样),老五(sdshdr5)从来不被应用 */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 低三位示意类型, 高五位示意字符串长度 */
char buf[];};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 字符串长度 */
uint8_t alloc; /* 调配长度 */
unsigned char flags; /* 低三位示意类型,高五位未应用 */
char buf[];};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* 字符串长度 */
uint16_t alloc; /* 调配长度 */
unsigned char flags; /* 低三位示意类型,高五位未应用 */
char buf[];};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* 字符串长度 */
uint32_t alloc; /* 调配长度 */
unsigned char flags; /* 低三位示意类型,高五位未应用 */
char buf[];};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* 字符串长度 */
uint64_t alloc; /* 调配长度 */
unsigned char flags; /* 低三位示意类型,高五位未应用 */
char buf[];};
知识点!这个很要害!!
\_\_attribute\_\_ ((\_\_packed\_\_))
待会你会看到如下代码:
(s)-(sizeof(struct sdshdr\#\#T)))、s[-1]、(char*)s-sdsHdrSize(s[-1])
这些指针之所以能够走位如此风骚,都归功于 \_\_attribute\_\_ ((\_\_packed\_\_))这个命令的意思是 勾销编译阶段的内存优化对齐性能.
ps: 对于内存补齐如果之前不晓得,请自行百度。
所以,该构造在内存中的构造如下:
这样看,之前那些风骚的走位就很明了了。
// s 减去 sdshdr 长度 = 指向 sdshdr 构造体的指针
(s)-(sizeof(struct sdshdr##T)))、// s 前一个地位 = flags
s[-1]
// 与 1 雷同成果
(char*)s-sdsHdrSize(s[-1])
有了下面的根底,看 sds.c 和 sds.h 里的代码就曾经很容易了,咱们重点看两个函数
- 创立 sds 字符串
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
/* 依据字符串的长度来决定 sds 的类型 */
char type = sdsReqType(initlen);
/* 老五被歧视了 */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
/* 计算 sdsHeader 的长度 */
int hdrlen = sdsHdrSize(type);
/* 对应 flags */
unsigned char *fp;
/* 开拓内存空间,+ 1 是为了最初放一个 \0,兼容传统 C 语言,入乡随俗 */
sh = s_malloc(hdrlen+initlen+1);
if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
/* 这走位,指向字符串开始的中央 */
s = (char*)sh+hdrlen;
/* 这走位,到 flags 了 */
fp = ((unsigned char*)s)-1;
/* 依据不同的类型,初始化 sdsHeader */
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;
}
- 动静扩大 sds 空间
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
/* avail = alloc-len */
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
/* 若剩下的空间足够,就不须要扩了 */
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
/* Redis 认为一旦被扩容了,* 那这个字符串被再次扩容的几率就很大,所以会在此基础上多加一些空间,* 避免频繁扩容
*/
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
/* 从新计算 type */
type = sdsReqType(newlen);
/* 老五又被歧视了 */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
/* 当原类型与新类型统一,则在原有根底是 realloc 空间即可 */
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* 否则须要从新 malloc 一整块空间,而后拷贝 */
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;
}
关注公众号:java 宝典