您的位置:首页 > 数据库 > Redis

Redis源码分析(五)——简单动态字符串(sds)

2014-10-18 21:34 746 查看
Sds(Simple Dynamic String)是Redis底层所使用的字符串表示,它被用在几乎所有的Redis模块中。

Redis是一个键值对数据库(K-V DB),数据库的值可以是字符串、集合、列表等多种对象,而键则只能是字符串对象。 由于char*类型的功能单一,抽象层次低,不能高效的支持一些Redis常用的操作(比如对字符串的追加操作和长度计算操作,而在Redis中对字符串的追加以及计算长度都是很常见的操纵,每次对char*字符串进行追加都必然导致内存的重新分配(realloc)这将严重影响性能),另外Reids的字符串表示还应该是二进制安全的。
所以在Redis的内部绝大部分使用的都是sds而不是char*来表示字符串。

二进制安全字符串:二进制安全是一种主要用于字符串操作函数相关的计算机编程术语。其本质就是将字符串中的所有字符都看作 原始的、无任何特殊格式意义的数据流。将所有字符视为按照一串0和1的形式编码的二进制数据,而不会对'\0'
,'\n' 等“特殊”的字符赋予不同的特殊含义。


Sds字符串总是以\0作为结束(sds对应的sdshdr的len不包括末尾的\0),由于sdshdr保存了字符串的长度len,因此在sds的中间可以包括\0字符(二进制安全的)。

在对字符串sds的操作时,总是把目标的C字符串cher*(sds)转换为 sdshdr(相当于string对象)进行操(及时更新sdshdr的已有字符数len,剩余空间free和实际存放字符串的buf等成员参数),然后再返回更新后的sds。 这样就使得C的字符串cher*具有了类似高级string的各种操作方法,从而能够方便的对C字符串sds计算长度以及追加等复杂操作。

Rdis通过对sds封装为sdshdr结构体,为sds字符串的操作提供了大量的操作方法,使得在Rdis中方便高效的使用字符串。

以下对sds的主要API函数进行分析,具体见注释。

sds.c /sds.h :

<span style="font-size:18px;">typedef char *sds;     //sds是C char*的别名

//动态字符串结构体
struct sdshdr {
unsigned int len;//当前buf已占用空间
unsigned int free;//buf剩余空间
char buf[];//实际保存字符串数据的地方
};

//计算sds的长度,返回的size_t类型的数值
static __inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}

//计算sds剩余可用长度
static __inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}

//创建一个指定长度的sds,接受一个C字符串作为初始值
sds sdsnewlen(const void *init, size_t initlen);
//创建一个sds,初始化为给定C字符串
sds sdsnew(const char *init);
//创建一个只包含空白串""的sds
sds sdsempty(void);
//获取sds的长度
size_t sdslen(const sds s);
//复制给定sds
sds sdsdup(const sds s);
//释放给定sds
void sdsfree(sds s);
//获取给定sds剩余可用空间
size_t sdsavail(const sds s);
//将给定sds的buf扩张至指定长度,无内容的部分用\0填充
sds sdsgrowzero(sds s, size_t len);
//将给定sds扩展至给定长度,并将一个给定C字符串追加到sds末尾
sds sdscatlen(sds s, const void *t, size_t len);
//将给定C字符串追加到给定sds末尾
sds sdscat(sds s, const char *t);
//将一个给定sds追加到另一个sds末尾
sds sdscatsds(sds s, const sds t);
//将一个给定C字符串的len部分复制到sds,需要时对sds进行扩展
sds sdscpylen(sds s, const char *t, size_t len);
//将一个给定C字符串复制到给定sds
sds sdscpy(sds s, const char *t);
//将已给 C字符串追加到给定sds末尾,并将sds内容格式化打印到另一给定C字符串
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
sds sdscatprintf(sds s, const char *fmt, ...);
#endif

sds sdscatfmt(sds s, char const *fmt, ...);//字符串格式化输出
sds sdstrim(sds s, const char *cset);//对给定sds缩剪
void sdsrange(sds s, int start, int end);//对给定sds进行按范围截取
void sdsupdatelen(sds s);//更新sds长度
void sdsclear(sds s);//清除sds内容,初始化为""
int sdscmp(const sds s1, const sds s2);//比较两个sds
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);//对sds分割子字符串
void sdsfreesplitres(sds *tokens, int count);//释放子串数组
void sdstolower(sds s);//将给定sds内容转换为小写字符
void sdstoupper(sds s);//将给定sds内容转换为大写字符
sds sdsfromlonglong(long long value);//创建数组sds??
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);//参数拆分
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);//字符映射
sds sdsjoin(char **argv, int argc, char *sep); //以分隔符连接字符串子数组构成新的字符串

/* Low level functions exposed to the user API */
/* 开放给使用者的API */
sds sdsMakeRoomFor(sds s, size_t addlen);//对给定sds对应的sdshdr结构的buf扩展
void sdsIncrLen(sds s, int incr);//增加sds的长度
sds sdsRemoveFreeSpace(sds s);//将buf多余的空间释放掉
size_t sdsAllocSize(sds s);//计算给定的sds的buf的内存空间大小

#endif
</span>


sds.c:

<span style="font-size:18px;">//创建一个指定长度的sds,接受一个C字符串作为初始值。创建初始化时buf剩余空间buf为0,在下次追加时按照给定扩展算法,更新free
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = (int)initlen;
sh->free = 0;
if (initlen && init)
memcpy(sh->buf, init, initlen);//从源init所指的内存地址的起始位置开始拷贝initlen个字节到目标buf所指的内存地址的起始位置中

sh->buf[initlen] = '\0';//填充buf的末尾
return (char*)sh->buf;//返回C字符串 buf
}

/* Create an empty (zero length) sds string. Even in this case the string
* always has an implicit null term. */
//创建一个空sds。(始终有一个\0字符)
sds sdsempty(void) {
return sdsnewlen("",0);
}

/* Create a new sds string starting from a null termined C string. */
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}</span>


扩容函数:(增加buf的free空间):如果原来free足够用则直接返回。否则,如果buf->len+addlen 小于SDS_MAX_PREALLOC(1M),则buf总空间扩为len+addlen的两倍;否则buf总空间扩为len+SDS_MAX_PREALLOC的两倍。 这也是sds由于char*的主要原因之一。在sds追加内容的时候,按需要触发扩容操作,并预留free的剩余空间,这样在下次追加时当free足够大时就可以不用再次重新分配空间(realloc),从而提高性能。以空间换时间,类似于动态数组vector。
对于预留的空间,可用 sdsRemoveFreeSpace(sds s)函数释放归还。然而free剩余空间的释放不是自动进行的,这可以通过设置系统,比如让系统定时自动释放掉free的剩余空间。

<span style="font-size:18px;">//扩容(增加buf的free空间):如果原来free足够用则直接返回。否则,如果buf->len+addlen 小于SDS_MAX_PREALLOC(1M),则buf总空间扩为len+addlen的两倍;否则buf总空间扩为len+SDS_MAX_PREALLOC的两倍
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);//现有的free
size_t len, newlen;

if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;

newsh->free = (int)(newlen - len);
return newsh->buf;
}

/* Reallocate the sds string so that it has no free space at the end. The
* contained string remains not altered, but next concatenation operations
* will require a reallocation.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
//去掉传入sds的free。 下次在该sds末尾追加时需要额外分配空间
sds sdsRemoveFreeSpace(sds s) {
struct sdshdr *sh;

sh = (void*) (s-(sizeof(struct sdshdr)));
sh = (struct sdshdr *)zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
sh->free = 0;
return sh->buf;
}
</span>


<span style="font-size:18px;">//增加buf的len(buf的free对应的减少incr的长度),当incr为负数时,进行反向操作。用于比如在sds后追加了字符串(free足够大,没有触发重新的内存分配)
//每次更新len之后都需要移动\0到对应位置
void sdsIncrLen(sds s, int incr) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

if (incr >= 0)
assert(sh->free >= (unsigned int)incr);//从free分配到len,确保free大等于incr
else
assert(sh->len >= (unsigned int)(-incr));//从len分配到free,确保len大等于-incr
sh->len += incr;
sh->free -= incr;
s[sh->len] = '\0';
}
</span>


sdscpylen:将给定C串复制到指定sds(覆盖原有内容),需要时扩容,t为二进制安全的字符串,因此需要传入其长度,不能通过strlen函数计算(strlen函数计算长度时遇到\0则结束),如果给定的字符串为非二进制安全的,则不需要传入len参数。

<span style="font-size:18px;">sds sdscpylen(sds s, const char *t, size_t len) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t totlen = sh->free+sh->len;

if (totlen < len) {
s = sdsMakeRoomFor(s,len-sh->len);
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
totlen = sh->free+sh->len;
}
memcpy(s, t, len);
s[len] = '\0';
sh->len = (int)len;
sh->free = (int)(totlen-len);
return s;
}</span>


把数值转换为字符串存放到s指定位置 的辅助函数。 s指向的字符串长度至少等于SDS_LLSTR_SIZE,返回转换所得的以\0结尾的字符串的长度

<span style="font-size:18px;">int sdsll2str(char *s, long long value) {
char *p, aux;
unsigned long long v;
size_t l;

/* Generate the string representation, this method produces
* an reversed string. */
//把数值通过取余逐位的存入字符串(为逆序)
v = (value < 0) ? -value : value;//取value绝对值
p = s;
do {
*p++ = '0'+(v%10);//字符'0'加偏移量到对应数值的字符
v /= 10;
} while(v);
if (value < 0) *p++ = '-';//若是负数,最后加上 符号

/* Compute length and add null term. */
l = p-s;
*p = '\0';

/* Reverse the string. */
p--;
//字符串逆转(首尾字符交换)
while(s < p) {
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
return (int)l;
}</span>


sds sdscatfmt(sds s, char const *fmt, ...):字符串格式化输出,输入原字符串,格式,参数

字符串格式化输出到sds,输入原字符串,格式,各需要格式化到sds的参数 */

例: sds x="--";

x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);

memcmp(x,"--Hello Hi! World -9223372036854775808,9223372036854775807--",60) == 0)

<span style="font-size:18px;">sds sdscatfmt(sds s, char const *fmt, ...) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t initlen = sdslen(s);
const char *f = fmt;
int i;
va_list ap;

va_start(ap,fmt);
f = fmt;    /* Next format specifier byte to process. */
i = initlen; /* Position of the next byte to write to dest str. */
//关键再次,以此比较输入的格式类型
while(*f) {
char next, *str;
unsigned int l;
long long num;
unsigned long long unum;

/* Make sure there is always space for at least 1 char. */
if (sh->free == 0) {
s = sdsMakeRoomFor(s,1);
sh = (void*) (s-(sizeof(struct sdshdr)));
}

switch(*f) {
case '%':
/*如果是%,记住百分号后面的类型操作值*/
next = *(f+1);
f++;
switch(next) {
case 's':
case 'S':
str = va_arg(ap,char*);
//判断普通的str,还是sds类型,计算长度的方法不一样
l = (next == 's') ? strlen(str) : sdslen(str);
if (sh->free < l) {
s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
//如果是字符串,直接复制到后面
memcpy(s+i,str,l);
sh->len += l;
sh->free -= l;
i += l;
break;
case 'i':
case 'I':
if (next == 'i')
num = va_arg(ap,int);
else
num = va_arg(ap,long long);
{
char buf[SDS_LLSTR_SIZE];
//如果是数字,调用添加数值字符串方法
l = sdsll2str(buf,num);
if (sh->free < l) {
s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
memcpy(s+i,buf,l);
sh->len += l;
sh->free -= l;
i += l;
}
break;
case 'u':
case 'U':
//无符号整型同上
if (next == 'u')
unum = va_arg(ap,unsigned int);
else
unum = va_arg(ap,unsigned long long);
{
char buf[SDS_LLSTR_SIZE];
l = sdsull2str(buf,unum);
if (sh->free < l) {
s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
memcpy(s+i,buf,l);
sh->len += l;
sh->free -= l;
i += l;
}
break;
default: /* Handle %% and generally %<unknown>. */
s[i++] = next;
sh->len += 1;
sh->free -= 1;
break;
}
break;
default:
//非操作类型,直接单字符添加
s[i++] = *f;
sh->len += 1;
sh->free -= 1;
break;
}
f++;
}
va_end(ap);

/* Add null-term */
s[i] = '\0';
return s;
}
</span>


<span style="font-size:18px;">//将给定的sds从左右两边剪去连续存在于cset中的所有字符
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
size_t len;

sp = start = s;//指向sds的头部
ep = end = s+sdslen(s)-1;//指向sds的最后一个字符
while(sp <= end && strchr(cset, *sp)) sp++;// 从sds的左边开始跳过所有连续在cset存在的字符
while(ep > start && strchr(cset, *ep)) ep--;//从sds的右边开始跳过所有连续在cset中存在的字符
len = (sp > ep) ? 0 : ((ep-sp)+1);//如此计算剩余的字符数:两个指针相减。如果左右移动已经交叉,则剩余空
if (sh->buf != sp) memmove(sh->buf, sp, len);//更新buf
sh->buf[len] = '\0';
sh->free = sh->free+(int)(sh->len-len);
sh->len = (int)len;
return s;
}</span>


小结:

对比C字符串,sds有以下特性:

* 可以高效的执行长度计算

* 可以高效的执行追加操作

* 二进制安全

sds会为追加操作进行优化: 加快追加操作的速度,并降低内存分配次数,代价是多占用了内存空间(buf的free预留空间),而且这些内存不会被主动释放。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: