Redis源码阅读笔记(1)-- 动态字符串sds

版权声明:欢迎关注公众号「Golang来了」交流。文章若有不妥之处请指正,欢迎转载,不过须注明出处~ https://blog.csdn.net/asd1126163471/article/details/54132872

一直对redis源码感兴趣,工作之余,总是抽空阅读,在这里记录下自己的阅读心得。废话不多说,一起来看下redis内部比较简单的数据结构–简单动态字符串SDS,源文件为sds.c和sds.h。

SDS数据结构定义

struct sdshdr {
    // 当前字符串的长度(不包括‘\0’)
    int len;
    // buf 中剩余可用空间的长度
    int free;
    // 字符数组
    char buf[];
};

Redis没有采用C语言中的字符串表示方式(以‘\0’结尾的字符数组),而是构建了SDS这种数据结构作为Redis默认的字符串,相对于C语言传统的字符串的好处在于:可动态地扩展内存、二进制安全和兼容部分C字符串函数。

基本操作函数

1、sds创建函数-sdsnewlen

//类型别名,用于指向 sdshdr 的 buf 属性
typedef char *sds;
sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
    // 根据是否有初始化内容,选择适当的内存分配方式
    //+1是因为需要额外的一个字节存放'\0'
    if (init) {
        // zmalloc 不初始化所分配的内存
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        // zcalloc 将分配的内存全部初始化为 0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    // 内存分配失败,返回
    if (sh == NULL) return NULL;
    // 设置初始化长度
    sh->len = initlen;
    // 新 sds 不预留任何空间
    sh->free = 0;
    // 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    // 返回 buf 部分,而不是整个 sdshdr
    return (char*)sh->buf;
}

需要注意的是函数的返回值:sh->buf,sds的很多基本操作都是先
通过sh->buf找到sdshdr结构体,例如接下去说到的sdslen。

2、获取sds中字符串的长度-sdslen

/* 获取字符串长度 */
static inline size_t sdslen(const sds s) {
    /* sizeof(struct sdshdr))的长度是8,而s指向sdshdr中的buf字符数组,所以 s-8 的结果就是sdshdr结构体的地址--sh,通过sh->len就可以获得字符串的长度
    */
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

类似的函数操作还有sdsavail–获取sds可用空间的长度等。

3、空间扩展函数-sdsMakeRoomFor

//最大预分配长度
#define SDS_MAX_PREALLOC (1024*1024)
//s 需要扩展空间的sdshdr,addlen 需要扩展的字节数
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    // sdsavail()函数可以获取 s 目前的空余空间长度
    size_t free = sdsavail(s);
    size_t len, newlen;
    // s 中有够用的字节空间,直接返回
    if (free >= addlen) return s;
    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));
    // s 最少需要的长度
    newlen = (len+addlen);
    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 1M
        newlen *= 2;
    else
        // 否则,多分配1M的空间
        newlen += SDS_MAX_PREALLOC;
    newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
    // 内存不足,分配失败,返回
    if (newsh == NULL) return NULL;
    // 更新 sds 的空余长度
    newsh->free = newlen - len;
    // 返回 sds
    return newsh->buf;
}

空间预分配策略可以减少修改字符串带来的内存重分配次数,同时可以避免缓存区溢出:当字符串(sds)操作函数需要对sds进行修改时,函数会先检查sds的空间是否满足修改之后的需要,如果不满足的话,函数会将sds的空间扩展至修改后所需要的空间大小,之后才会执行修改操作,避免缓存区溢出。
缓存区溢出:例如在使用strcat()函数时,如果不事先分配所需要的内存空间时,就会容易发生缓存区溢出。

4、其他操作函数

sds还提供了许多操作函数,这里就不一一详细做源码分析了,只说明函数的用途

sds sdscat(sds s, const char *t);  //字符串连接函数
sds sdsempty(void);     //清空sds
sds sdscpy(sds s, const char *t); // 字符串的复制
sds sdscatfmt(sds s, char const *fmt, ...);   //字符串格式化输出
sds sdstrim(sds s, const char *cset);       //字符串缩减
sds sdsRemoveFreeSpace(sds s);   //回收sds空余空间

总结

第一次写博客,如果有错误的地方或者不妥之处,楼主非常欢迎大家可以回帖讨论,定及时回复。希望通过对这几个函数的解析,起到抛砖引玉的作用,读者能够更深入地了解其他函数的作用以及实现方式,体会作者的编程思想,同时,这也是一个能够提高自己C语言的方式。
很感谢黄健宏老师的《Redis设计与实现》,这本书给了我很大的帮助,支持!

猜你喜欢

转载自blog.csdn.net/asd1126163471/article/details/54132872