redis源码分析-sds字符串

来源:互联网 发布:模拟别人说话声音软件 编辑:程序博客网 时间:2024/04/28 17:56

1.简介

在c语言中,一般使用char定义字符串类型,而redis却不一样,采用sds结构进行存储。那么为什么redis弃用char而改用sds呢?这样做是基于哪些方面的考虑?这样做的优缺点各有哪些呢?

2.SDS结构

带着上面的疑问,我们先回到原点,看下sds的数据结构(以3.0版本为例)(sds结构及相关操作代码位于src/sds.h、src/sds.c文件)。

struct sdshdr {    int len;    int free;    char buf[]; };

从上面的结构可以看出,sds字符串比c字符串多2个属性,占用的字节数比c字符串多 4+4+1(sds)-1(char) = 8个。
- len: 已占用空间长度
- free:剩余空间长度
- buf: 字符串数据

下面通过简单的实例讲解sds字符串创建、追加、释放操作,以便加深对sds结构的理解。

a).sds“创建”操作:定义一个“hello”的str字符串,如下:

sds sdsnewlen(const void *init, size_t initlen) {    struct sdshdr *sh;    //判断是否有指定内容,如果有,则不重置分配内存的内容。如果没有,则填充0    if (init) {        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);    } else {        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);    }    if (sh == NULL) return NULL;    //指定字符串长度    sh->len = initlen;    //剩余空间默认为0    sh->free = 0;    if (initlen && init)        //将字符串填充至buf        memcpy(sh->buf, init, initlen);    // 以 \0 结尾    sh->buf[initlen] = '\0';    return (char*)sh->buf;}

str的SDS结构图:
这里写图片描述

b).sds追加:当我们将“ world”和“!”分别追加至str字符串,redis会执行以下函数进行处理:

sds sdscat(sds s, const char *t) {    return sdscatlen(s, t, strlen(t));}//拼接字符串sds sdscatlen(sds s, const void *t, size_t len) {    struct sdshdr *sh;    // 原有字符串长度    size_t curlen = sdslen(s);    // 扩展 sds 空间    s = sdsMakeRoomFor(s,len);    // 申请空间失败    if (s == NULL) return NULL;    //将新字符串copy至字符串尾部    sh = (void*) (s-(sizeof(struct sdshdr)));    memcpy(s+curlen, t, len);    // 更新属性    sh->len = curlen+len;    sh->free = sh->free-len;    // 添加新结尾符号    s[curlen+len] = '\0';    return s;}//扩展空间sds sdsMakeRoomFor(sds s, size_t addlen) {    struct sdshdr *sh, *newsh;    // 获取 s 目前的剩余空间长度    size_t free = sdsavail(s);    size_t len, newlen;    //如果free>=addlen 则无需申请空间。    if (free >= addlen) return s;    len = sdslen(s);    sh = (void*) (s-(sizeof(struct sdshdr)));    //新字符串长度    newlen = (len+addlen);    //如果newlen<SDS_MAX_PREALLOC时,按照2倍*新字符串长度进行分配空间    //否则,申请的空间=新字符串长度+SDS_MAX_PREALLOC    //SDS_MAX_PREALLOC 默认为1024*1024 (1M)    if (newlen < SDS_MAX_PREALLOC)        newlen *= 2;    else        newlen += SDS_MAX_PREALLOC;    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);    //申请空间失败,返回null    if (newsh == NULL) return NULL;    //重置新字符串的剩余空间属性    newsh->free = newlen - len;    // 返回 sds    return newsh->buf;}

追加“ world”后str的SDS结构图:
这里写图片描述

此时str->free=11,当再次追加“!”至str时,系统无需重新分配空间,只须将新字符串copy至尾部即可。SDS结构图如下:
这里写图片描述

c).sds释放空间操作,代码如下:

void sdsfree(sds s) {    if (s == NULL) return;    zfree(s-sizeof(struct sdshdr));}

3.SDS与C字符串区别

a).sds占用空间比c字符串多8个字节4(len)+4(free);

b).相比C字符串,sds计算长度时间复杂度降低了很多,前者O(n),后者O(1).

c).减少内存分配次数:内存分配是一个费时费力的“工程”。当redis作为数据库时,数据变更会经常发生。使用SDS保存字符串数据能够有效地减少内存分配次数。

d).二进制安全:我们都知道C字符串的内容不能包含空字符,否则最先被程序读入的空字符会被误认为字符串的结束。而这一限制使得C字符串只能保存纯文本数据,无法保存像图片、视频、压缩文件等二进制文件。而sds字符串不一样,字符串长度是根据len属性来决定的,即使内容中含有空字符串,则对数据的完整性也没有任何影响。

4.兼容C字符串函数

细心的读者可以发现,sds的buf与c字符串一样,在字符串的末尾增加空字符串来表示结束。这样就可以保证sds字符串沿用C语言字符串的部分函数,而无需进行重写。

5.总结

a).sds字符串采用以“空间换时间”的做法达到提升性能的目的。
b).sds字符串功能更为强大,能支持多种格式存储数据。

0 0
原创粉丝点击