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
1/* 因为生的跟别人不一样(内部结构不一样),老五(sdshdr5)从来不被使用 */ 2struct __attribute__ ((__packed__)) sdshdr5 { 3 unsigned char flags; /* 低三位表示类型, 高五位表示字符串长度 */ 4 char buf[]; 5}; 6struct __attribute__ ((__packed__)) sdshdr8 { 7 uint8_t len; /* 字符串长度*/ 8 uint8_t alloc; /* 分配长度 */ 9 unsigned char flags; /* 低三位表示类型,高五位未使用 */ 10 char buf[]; 11}; 12struct __attribute__ ((__packed__)) sdshdr16 { 13 uint16_t len; /* 字符串长度*/ 14 uint16_t alloc; /* 分配长度 */ 15 unsigned char flags; /* 低三位表示类型,高五位未使用 */ 16 char buf[]; 17}; 18struct __attribute__ ((__packed__)) sdshdr32 { 19 uint32_t len; /* 字符串长度*/ 20 uint32_t alloc; /* 分配长度 */ 21 unsigned char flags; /* 低三位表示类型,高五位未使用 */ 22 char buf[]; 23}; 24struct __attribute__ ((__packed__)) sdshdr64 { 25 uint64_t len; /* 字符串长度*/ 26 uint64_t alloc; /* 分配长度 */ 27 unsigned char flags; /* 低三位表示类型,高五位未使用 */ 28 char buf[]; 29};
知识点!这个很关键!!
__attribute__ ((__packed__))
待会你会看到如下代码:
(s)-(sizeof(struct sdshdr##T))) 、s[-1]、(char*)s-sdsHdrSize(s[-1])
这些指针之所以可以走位如此风骚,都归功于 __attribute__ ((__packed__))这个命令的意思是 取消编译阶段的内存优化对齐功能.
ps: 关于内存补齐如果之前不知道,请自行百度。
所以,该结构在内存中的结构如下:

这样看,之前那些风骚的走位就很明了了。
1// s减去sdshdr长度 = 指向sdshdr结构体的指针 2(s)-(sizeof(struct sdshdr##T))) 、 3// s前一个位置 = flags 4s[-1] 5// 与1相同效果 6(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/* 开辟内存空间,+1是为了最后放一个\0,兼容传统C语言,入乡随俗 */ 2sh = s_malloc(hdrlen+initlen+1); 3if (!init) 4 memset(sh, 0, hdrlen+initlen+1); 5if (sh == NULL) return NULL; 6/* 这走位,指向字符串开始的地方 */ 7s = (char*)sh+hdrlen; 8/* 这走位,到flags了 */ 9fp = ((unsigned char*)s)-1; 10 11/* 根据不同的类型,初始化sdsHeader */ 12switch(type) { 13 case SDS_TYPE_5: { 14 *fp = type | (initlen << SDS_TYPE_BITS); 15 break; 16 } 17 case SDS_TYPE_8: { 18 SDS_HDR_VAR(8,s); 19 sh->len = initlen; 20 sh->alloc = initlen; 21 *fp = type; 22 break; 23 } 24 case SDS_TYPE_16: { 25 SDS_HDR_VAR(16,s); 26 sh->len = initlen; 27 sh->alloc = initlen; 28 *fp = type; 29 break; 30 } 31 case SDS_TYPE_32: { 32 SDS_HDR_VAR(32,s); 33 sh->len = initlen; 34 sh->alloc = initlen; 35 *fp = type; 36 break; 37 } 38 case SDS_TYPE_64: { 39 SDS_HDR_VAR(64,s); 40 sh->len = initlen; 41 sh->alloc = initlen; 42 *fp = type; 43 break; 44 } 45} 46/* 字符串赋值 */ 47if (initlen && init) 48 memcpy(s, init, initlen); 49s[initlen] = '\0'; 50return 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;
1/* 若剩下的空间足够,就不需要扩了 */ 2if (avail >= addlen) return s; 3 4len = sdslen(s); 5sh = (char*)s-sdsHdrSize(oldtype); 6newlen = (len+addlen); 7/* Redis认为一旦被扩容了, 8 * 那这个字符串被再次扩容的几率就很大,所以会在此基础上多加一些空间, 9 * 防止频繁扩容 10 */ 11if (newlen < SDS_MAX_PREALLOC) 12 newlen *= 2; 13else 14 newlen += SDS_MAX_PREALLOC; 15/* 重新计算type */ 16type = sdsReqType(newlen); 17 18/* 老五又被歧视了 */ 19if (type == SDS_TYPE_5) type = SDS_TYPE_8; 20 21hdrlen = sdsHdrSize(type); 22if (oldtype==type) { 23 /* 当原类型与新类型一致,则在原有基础是realloc空间即可 */ 24 newsh = s_realloc(sh, hdrlen+newlen+1); 25 if (newsh == NULL) return NULL; 26 s = (char*)newsh+hdrlen; 27} else { 28 /* 否则需要重新malloc一整块空间,然后拷贝 */ 29 newsh = s_malloc(hdrlen+newlen+1); 30 if (newsh == NULL) return NULL; 31 memcpy((char*)newsh+hdrlen, s, len+1); 32 s_free(sh); 33 s = (char*)newsh+hdrlen; 34 s[-1] = type; 35 sdssetlen(s, len); 36} 37sdssetalloc(s, newlen); 38return s;}
关注公众号:java宝典
