SDS---A C dynamic strings library 一个动态字符串库
优点:
二进制安全[\0可在字符串内出现];
通过扩充属性,可快速获取字符串长度[空间换时间];
兼容C的字符串操作函数。
缺点:
- 许多函数传入字符串之后不确定是否改变原值,所以需要重新赋值,不然容易导致Bug,如str=sdscat(str, "test sdscat"),函数返回值需要赋值到str上。
- 字符串数据共享问题,一处修改,其余所有都需修改。
一个简单的结构(sdshdr8)
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[];
};
// sizeof(struct sdshdr8) = 3
注意,__attribute__ ((__packed__))可以让结构体按照紧凑排列的方式来占用内存,所以可以很方便的计算内存地址以获取指向sdshdr头部的起始地址的指针[sds减去头部大小]
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
又或,后移1个字节即可取到flags
sds s; unsigned char flags = s[-1];
sds定义了5种类似的结构---为了节省内存(为了节省内存真是无所不用至极,学习!!!),分别是sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。
其中sdshdr5并不使用,使用flags的高5位说明字符串长度。(也就是说长度比较小时推荐使用C语言原生字符串)。
内联函数
// 获取buf中sdshdr存储的字符串的长度
static inline size_t sdslen(const sds s)sdsavail
// 可用存储空间长度
static inline size_t sdsavail(const sds s)
// 设置使用长度
static inline void sdssetlen(sds s, size_t newlen)
// 增加使用长度
static inline void sdsinclen(sds s, size_t inc)
// sdsalloc() = sdsavail() + sdslen()
static inline size_t sdsalloc(const sds s)
// 设置alloc属性
static inline void sdssetalloc(sds s, size_t newlen)
一些比较重要的函数
1.根据指定内容和指定长度创建一个sds字符串
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this.
* 空字符串和type 5默认设为SDS_TYPE_8
*/
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */
sh = s_malloc(hdrlen+initlen+1);
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
// 如果没有指定初始内容,分配空间,设置为0
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
// 地址查询-->buf[内存紧凑]
s = (char*)sh+hdrlen;
// flags指针
fp = ((unsigned char*)s)-1;
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;
}
//... 为了篇幅去掉类似的代码
}
// 赋值
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}
创建一个字符串:
sdsnewlen("redis", 5)
# sh=s_malloc(sizeof(struct hdrlen)+initlen+1); // initlen为参数的长度
------------
|5|0|redis|
------------
^ ^
sh sh->buf
注意,sdsnewlen返回sh->buf
2.扩容函数(free属性)
/* Enlarge the free space at the end of the sds string so that the caller
* is sure that after calling this function can overwrite up to addlen
* bytes after the end of the string, plus one more byte for nul term.
*
* Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have.
*
* 扩容函数(free空间)
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
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);
// 扩容机制 小于1M,现有空间乘以2;超过1M,则扩容1M
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);
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);
}
// 设置alloc字段
sdssetalloc(s, newlen);
return s;
}
参考源码尾部的sdsTest
内存使用
空间与时间之间的平衡
网友评论