Redis源码解读(二)简单动态字符串

134 阅读2分钟

SDS的结构

以下是sds的结构

// sdshdr 结构
struct sdshdr {

    // buf 已占用长度
    int len;

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

    // 实际保存字符串数据的地方
    // 利用c99(C99 specification 6.7.2.1.16)中引入的 flexible array member,通过buf来引用sdshdr后面的地址,
    // 详情google "flexible array member"
    char buf[];
    //这个数组没有大小,是所谓的柔性数组,是不占据内存大小的
};

buf指向一个字符串,保存的'\0'不算在len属性里面,通过使用sds而不是C字符串,获取长度即是O(1)的

其次,由于可能频繁修改字符串,因而防止内存的重分配对性能造成影响,sds采取空间预分配和惰性空间释放两种优化策略

  • 空间预分配 如果修改后sds的长度小于1MB,那么会分配相同长度的free空间,如果大于等于1MB,那么会直接分配1MB
  • 惰性空间释放 通过惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配操作,并为将来可能有的增长操作提供了优化

SDS是二进制安全的

主要体现在sds是以len来判断字符串的结尾而不是'\0',因此满足了Redis可以存储任意格式的二进制数据

SDS的函数解读

sdsnewlen

//sdsnewlen根据init字符串和initlen初始化sdsnewlen
sds sdsnewlen(const void *init, size_t initlen) {

    struct sdshdr *sh;

    // 有 init ?
    // O(N)
    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);//分配结构体空间和buf空间
    } 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;
}

sdsnew

sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

sdsempty

sds sdsempty(void) {
    // O(N)
    return sdsnewlen("",0);
}

sdsfree

void sdsfree(sds s) {
    if (s == NULL) return;
    zfree(s-sizeof(struct sdshdr));
}
/*
 * 更新给定 sds 所对应的 sdshdr 结构的 free 和 len 属性
 *
 * T = O(n)
 */
void sdsupdatelen(sds s) {
    /*
    在初始化即sdsnewlen时,sh是指向buf位置的,此处减去是为了重新指回结构体的头部
    */
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    // 计算正确的 buf 长度
    int reallen = strlen(s);

    // 更新属性
    sh->free += (sh->len-reallen);
    sh->len = reallen;
}
/*
 * 计算给定 sds buf 的内存长度(包括已使用和未使用的)
 *
 * T = O(1)
 */
size_t sdsAllocSize(sds s) {

    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    /*
    * 此处是将指针指向buf后加上len和free的长度以及'\0'
    */
    return sizeof(*sh)+sh->len+sh->free+1;
}

以上是sds中常见的函数