Redis源码学习1——基本数据结构sds

最近好几次用到redis,但是一直没有时间来好好学习下redis的原理,打算最近花一个月的空余时间来整理学习下redis的源码。redis本身非常简洁,下载源码直接make就ok了,而且似乎没有依赖第三方库什么的。先从基本的数据结构开始把,主要参考了http://www.redisbook.com/en/latest/index.html#, 非常赞的关于redis 的站点,作者huangz1990对redis的分析非常清楚易懂,作者提供的有详细注释的源代码地址:https://github.com/huangz1990/annotated_redis_source。在此基础上我读读源码。好久不搞c语言了,看起来真陌生。先从基本数据结构看起吧,首先分析sds.c文件。

1.sdshdr结构

// sds 类型
typedef char *sds;

// sdshdr 结构
struct sdshdr {

    // buf 已占用长度
    int len;

    // buf 剩余可用长度
    int free;

    // 实际保存字符串数据的地方
    char buf[];
};

看来sdshdr结构还是挺简单的,主要用来管理字符串,之所以另外设计出一个结构,作者自有用意了,管理字符串比原生的字符串方便高效许多。

这样我们如果构建一个字符串“hello world”, 那么在sdshdr结构体中表示起来就是这个样子的了:
struct sdshdr {
    len = 11;
    free = 0;
    buf = "hello world\0";  // buf 的实际长度为 len + 1
};

2.sdsnew构建字符串

构建字符串的函数sdsnew如下:
/*
 * 根据给定初始化值 init ,创建 sds
 * 如果 init 为 NULL ,那么创建一个 buf 内只包含 \0 终结符的 sds
 *
 * T = O(N)
 */
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

可以发现,sdsnew调用sdsnewlen来构建字符串。sdsnewlen代码如下:

/*
 * 创建一个指定长度的 sds 
 * 如果给定了初始化值 init 的话,那么将 init 复制到 sds 的 buf 当中
 *
 * T = O(N)
 */
sds sdsnewlen(const void *init, size_t initlen) {

    struct sdshdr *sh;

    // 有 init ?
    // O(N)
    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;
    sh->free = 0;

    // 如果给定了 init 且 initlen 不为 0 的话
    // 那么将 init 的内容复制至 sds buf
    // O(N)
    if (initlen && init)
        memcpy(sh->buf, init, initlen);

    // 加上终结符
    sh->buf[initlen] = '\0';

    // 返回 buf 而不是整个 sdshdr
    return (char*)sh->buf;
}
这里主要是根据字符串长度来分配内存空间。如果指定了init,则调用zmalloc分配内存,否则调用zcalloc分配内存。需要注意的是,redis对内存分配和释放过程做了封装,具体在下一篇文章再单独分析。zmalloc和zcalloc的区别无非是zmalloc不会初始化分配的内存,而zcalloc则会将分配的内存初始化为0。分配内存大小都为initlen+1+sdshdr大小。需要注意的是,返回值为sdshdr结构体的buf。buf是一个柔性数组,在结构体中并没有设定元素数目。所以,结构体sdshdr本身的大小其实是2个int的大小。

3.sdscatsds 字符串扩展

使用sdshdr结构体,在append字符串的时候就非常方便了。如果我们在“hello world”后append“ again!“,则sdshdr会更新为下面这个样子:
struct sdshdr {
    len = 18;
    free = 18;
    buf = "hello world again!\0                  ";     // 空白的地方为预分配空间,共 18 + 18 + 1 个字节
}
append操作在函数sdscat中实现,而sdscat又是调用sdscatlen中实现。
/*
 * 将一个 char 数组拼接到 sds 末尾 
 *
 * T = O(N)
 */
sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}
sdscatlen首先获取当前sds的长度,然后扩展大小。并将字符串拼接到原来的sds末尾。更新len和free大小,并设定终结符\0。
/*
 * 按长度 len 扩展 sds ,并将 t 拼接到 sds 的末尾
 *
 * T = O(N)
 */
sds sdscatlen(sds s, const void *t, size_t len) {

    struct sdshdr *sh;

    size_t curlen = sdslen(s);

    // O(N)
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;

    // 复制
    // O(N)
    memcpy(s+curlen, t, len);

    // 更新 len 和 free 属性
    // O(1)
    sh = (void*) (s-(sizeof(struct sdshdr)));
    sh->len = curlen+len;
    sh->free = sh->free-len;

    // 终结符
    // O(1)
    s[curlen+len] = '\0';

    return s;
}

append操作的时候内存分配策略由sdsMakeRoomFor函数负责,代码如下:
sds sdsMakeRoomFor(
    sds s,
    size_t addlen   // 需要增加的空间长度
)
{
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s); //根据sds找出sdshdr结构体指针位置,并得到结构体free字段的值
    size_t len, newlen;

    // 剩余空间可以满足需求,无须扩展
    if (free >= addlen) return s;

    sh = (void*) (s-(sizeof(struct sdshdr)));

    // 目前 buf 长度
    len = sdslen(s);
    // 新 buf 长度
    newlen = (len+addlen);
    // 如果新 buf 长度小于 SDS_MAX_PREALLOC 长度
    // 那么将 buf 的长度设为新 buf 长度的两倍
    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 = newlen - len;

    return newsh->buf;
}
如果free大于要增加的长度,则直接返回。否则,则需要扩展。在2.6版本中,SDS_MAX_PREALLOC大小为1M,即如果newlen大于1M,则增加1M。否则则newlen更新为原来的两倍。并更新free的值,返回buf。

4.sdsclear 清除

清除sds的函数为sdsclear,代码如下:
 void sdsclear(sds s) {
 
     struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); //获取sdshdr结构体地址
 
     sh->free += sh->len; //free加上字符串长度
     sh->len = 0;  //len设为0
     sh->buf[0] = '\0'; //buf第0个元素设为\0
 }
针对前面的“hello world”构建的sds调用sdsclear,则此时sdshdr结构体看起来就如下面一样了:

struct sdshdr {
    len = 0;
    free = 11;
    buf = "\0ello world\0";  
};




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值