redis源码分析-sds字符串

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值