redis6.2源码解析-简单动态字符串

1 SDS的介绍

Redis 没有直接使用C原生的字符串, 而是自己构建了一种简单动态字符串(simple dynamic string), 简称SDS. redis只有在一些无须对字符串进行修改的地方, 如打印日志, 才会用原生的C字符串.

sds的优化点:

c 字符串 原因 SDS 优化
获取字符串长度复杂度为O(N) 没有记录字符串长度, 需要遍历数组 获取字符串长度复杂度为O(1) 记录字符串的长度
API是不安全的, 可能会造成缓冲区溢出 字符数组没有长度 API是安全的, 不会造成缓冲区溢出 SDS 空间分配策略会自动扩展数组的长度
修改字符串长度N次必然会执行N次内存重分配 字符串就是字符数组, 增加字符需要重分配空间, 减少字符需要缩减空间 修改字符串长度N次最多需要执行N次内存重分配 1. 空间预分配 2. 惰性空间释放
只能保存文本数据 字符数组以'\0'为字符串结束符 可以保存文本和二进制数据 以len长度为是否结束
可以使用所有的<string.h>库中的函数 原C生字符串肯定可以用原生的库 可以使用所有的<string.h>库中一部分的函数 sds也会在字符最后添加字符串结束符

2 sds.h 源码解析

2.1 SDS 结构体

redis4.0之后, 开始使用5种header结构体来定义SDS, redis会自动根据字符串的不同的长度选择不同的结构体来保存字符串, 从而节省内存的使用.
reids虽然定义了5位长度的sdshdr5, 但是从来不会使用它, 估计是它能存的字符串太少了, 2^5=32个字符.


//为字符数组定义别名, 为sds
typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
//sds结构体从4.0开始, 开始使用这5种sdshdr${n}的定义, 用于更合理地分配内存, 注意, 只是定义了 sdshdr5, 但是不会使用.
// __attribute__ ((__packed__)) 是指定编译器属性, 非语言特性, packed属性的主要目的是让编译器更紧凑地使用内存, 具体可以google一下
//最长 2^5-1 长度的 sdshdr
struct __attribute__ ((__packed__)) sdshdr5 {
    //低三位保存类型标志, 高5位用于保存字符串长度. 最多能保存5bit长度的字符串
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
//最长 2^8-1 长度的 sdshdr
struct __attribute__ ((__packed__)) sdshdr8 {
    //len 表示已使用长度
    uint8_t len; /* used */
    //buf分配的总长度, 也就是数组的总大小, 剩余大小 = alloc - len
    uint8_t alloc; /* excluding the header and null terminator */
    //低3位保存类型标志
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    //字符数组
    char buf[];
};
//最长 2^16-1 长度的 sdshdr
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
//最长 2^32-1 长度的 sdshdr
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
//最长 2^64-1 长度的 sdshdr
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

//flags 的5种类型值
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
//低三位的掩码,  也就是 00000111
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
  • sds 的结构体主要由四个属性组成, 分别是字符串长度len, sds的内存空间大小alloc, sds的类型 flags, 和字符数组 buf[]. 注意, sdshdr5只有两个属性, 其中flags低三位表示类型, 高5位表示字符串长度.
  • attribute ((packed)) 是指定编译器属性, 非语言特性, packed属性的主要目的是让编译器更紧凑地使用内存, 具体可以google一下

2.2 sds.h其他变量和方法的定义

//宏定义中, 用#于把宏参数变成一个字符串, 用##把两个宏参数粘合在一起
//获取 sdshdr 引用地址, 并且将地址放到 sh 变量中
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
//这里为什么 s - sizeof(struct sdshdr##T) 就能得到 sdshdr##T 呢
//可以看一下 sdshdr##T 的内存结构, 如: sdshdr8, 代表字符数组引用的sds在结构体的最后, 数组名不占空间, 我们可以得到
// 结构体的引用地址 = 字符数组的引用地址 - 结体的大小
//获取 sdshdr 引用地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
//左移三位, 也就是获取高5位作为返回值
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

//内联函数, 用于获取sds字符串的长度
static inline size_t sdslen(const sds s) {
    //获取数组下标外, 前一个char大小的值, 也就相当于 SDSHDR 的 flag
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

//内联函数, 用于获取sds的剩余空间, 剩余空间 = alloc - len
static inline size_t sdsavail(const sds s) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5: {
            return 0;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            return sh->alloc - sh->len;
        }
    }
    return 0;
}

//设置sds的长度
static inline void sdssetlen(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                unsigned char *fp = ((unsigned char*)s)-1;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->len = newlen;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->len = newlen;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->len = newlen;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->len = newlen;
            break;
    }
}

//扩展sds的长度
static inline void sdsinclen(sds s, size_t inc) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                unsigned char *fp = ((unsigned char*)s)-1;
                unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->len += inc;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->len += inc;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->len += inc;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->len += inc;
            break;
    }
}

//获取数组分配的大小
/* sdsalloc() = sdsavail() + sdslen() */
static inline size_t sdsalloc(const sds s) {
    //获取 flag 标记
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->alloc;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->alloc;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->alloc;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->alloc;
    }
    return 0;
}

//重置sds已分配容量的大小
static inline void sdssetalloc(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            /* Nothing to do, this type has no total allocation info. */
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->alloc = newlen;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->alloc = newlen;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->alloc = newlen;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->alloc = newlen;
            break;
    }
}

//创建给定长度的 sds
sds sdsnewlen(const void *init, size_t initlen);
sds sdstrynewlen(const void *init, size_t initlen);
//创建字符串数组创建 sds
sds sdsnew(const char *init);
//创建空在的 sds
sds sdsempty(void);
//复制sds对象返回
sds sdsdup(const sds s);
//释放sds
void sdsfree(sds s);
//用空字符串扩展sds的长度
sds sdsgrowzero(sds s, size_t len);
//将二进制安全的字符串附加到现有的 buf 数组之后
sds sdscatlen(sds s, const void *t, size_t len);
//sds拼接字符串
sds sdscat(sds s, const char *t);
//拼接两sds
sds sdscatsds(sds s, const sds t);
//丢弃 sds 字符数组中的原内容,将长为 len 的字符串拷贝至 sds 的 buf 中
sds sdscpylen(sds s, const char *t, size_t len);
//将给定的C字符串复制到sds里面, 覆盖SDS原有的字符串
sds sdscpy(sds s, const char *t);

//将格式化字符串拼接到 sds 之后
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
    __attribute__((format(printf, 2, 3)));
#else
sds sdscatprintf(sds s, const char *fmt, ...);
#endif

sds sdscatfmt(sds s, char const *fmt, ...);
//接受一个SDS和一个字符串作为参数, 从SDS中移除所有在D字符串中出现过的字符
sds sdstrim(sds s, const char *cset);
//依据 start 和 end 索引下标修剪 sds 字符串
void sdsrange(sds s, ssize_t start, ssize_t end);
//更新sds长度
void sdsupdatelen(sds s);
//清空sds内空
void sdsclear(sds s);
//对比两个sds是否相同
int sdscmp(const sds s1, const sds s2);
//使用长为 seplen 的二进制安全字符串 sep 作为分隔符,将长为 len 的二进制安全字符串 s 分割成 count 个 sds 字符串
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
//释放 sdssplitlen 生成的动态数组的内存
void sdsfreesplitres(sds *tokens, int count);
//字符串变小写
void sdstolower(sds s);
//字符串变大小
void sdstoupper(sds s);
//将long long 值转成 sds
sds sdsfromlonglong(long long value);
//处理特殊字符, 非打印字符会转成16进制打印, 相当于将字符串变成可打印的
sds sdscatrepr(sds s, const char *p, size_t len);
//解析命令行参数, 返回sds数组和参数个数
sds *sdssplitargs(const char *line, int *argc);
//遍历 sds 字符串,将在字符串 from 中出现的字符替换成 to 中对应位置的字符
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
//使用 C 风格字符串 sep 作为分隔符,将 C 风格字符串数组拼接为一个 sds
sds sdsjoin(char **argv, int argc, char *sep);
//以 sep 字符串为分割符, 将sds数组拼接成字符串
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);

/* Callback for sdstemplate. The function gets called by sdstemplate
 * every time a variable needs to be expanded. The variable name is
 * provided as variable, and the callback is expected to return a
 * substitution value. Returning a NULL indicates an error.
 */
//模版中变量的处理函数指针
typedef sds (*sdstemplate_callback_t)(const sds variable, void *arg);
//处理字符串模板, 如: "my name is {1}"
sds sdstemplate(const char *template, sdstemplate_callback_t cb_func, void *cb_arg);

/* Low level functions exposed to the user API */
//空间预分配, 减少内存重分配次数
sds sdsMakeRoomFor(sds s, size_t addlen);
//sds增加长度
void sdsIncrLen(sds s, ssize_t incr);
//回收sds空闲的内存空间
sds sdsRemoveFreeSpace(sds s);
//获取sds分配的空间大小
size_t sdsAllocSize(sds s);
//获取sds分配内存的指针, 也就是sdsHdr对象的指针
void *sdsAllocPtr(sds s);

/* Export the allocator used by SDS to the program using SDS.
 * Sometimes the program SDS is linked to, may use a different set of
 * allocators, but may want to allocate or free things that SDS will
 * respectively free or allocate. */
//分配sds内存
void *sds_malloc(size_t size);
//重分配sds内存
void *sds_realloc(void *ptr, size_t size);
//释放sds内存
void sds_free(void *ptr);
  • sds.h 定义了sds相关的方法声明, 具体每个方法的作用可以看上面的注释

3 sds.c 的源码解析

3.1 sdsHdr 相关属性的获取

//根据给定的类型获取 sdsHdr 的结构体的大小
static inline int sdsHdrSize(char type) {
    switch(type&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return sizeof(struct sdshdr5);
        case SDS_TYPE_8:
            return sizeof(struct sdshdr8);
        case SDS_TYPE_16:
            return sizeof(struct sdshdr16);
        case SDS_TYPE_32:
            return sizeof(struct sdshdr32);
        case SDS_TYPE_64:
            return sizeof(struct sdshdr64);
    }
    return 0;
}

//根据字符串的大小, 返回对应的sdsHdr的类型
static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)
        return SDS_TYPE_5;
    if (string_size < 1<<8)
        return SDS_TYPE_8;
    if (string_size < 1<<16)
        return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
    if (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
#else
    return SDS_TYPE_32;
#endif
}

//根据sdsHdr的类型, 返回此类型能存字符串的最大长度
static inline size_t sdsTypeMaxSize(char type) {
    if (type == SDS_TYPE_5)
        return (1<<5) - 1;
    if (type == SDS_TYPE_8)
        return (1<<8) - 1;
    if (type == SDS_TYPE_16)
        return (1<<16) - 1;
#if (LONG_MAX == LLONG_MAX)
    if (type == SDS_TYPE_32)
        return (1ll<<32) - 1;
#endif
    return -1; /* this is equivalent to the max SDS_TYPE_64 or SDS_TYPE_32 */
}

3.2 创建新SDS字符串相关方法

//用于标识, 创建sds时, 不初始化字符数组
extern const char *SDS_NOINIT;

//如果init指针是NULL, 则将内存初始化为0, 如果init是SDS_NOINIT, 则不进行初始化
//trymalloc 用于指定分配内存的方法
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
    void *sh;
    sds s;
    //根据字符串长度, 获取sdsHdr的类型
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    //如果类型是 5, 或者初始字符串长度是 0 , 则默认给 8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    //获取 sdsHdr 的结构体的长度
    int hdrlen = sdsHdrSize(type);
    //flag字段的指针
    unsigned char *fp; /* flags pointer. */
    //字符数组可使用的长度
    size_t usable;

    //确保不会溢出
    assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
    //分配sds内存, sds内存大小 = sdsHdr + 字符串长度 + 空标识符
    //trymalloc=1, 则尝试分配内存, 分配不了则返回 NULL, trymalloc=0则强制分配内存, 分配不了, 则直接打错误日志, 并且退出程序
    sh = trymalloc?
        s_trymalloc_usable(hdrlen+initlen+1, &usable) :
        s_malloc_usable(hdrlen+initlen+1, &usable);
    //没分配到, 则返回 NULL
    if (sh == NULL) return NULL;
    //如果指定不初始化字符数组, 则init设置为空
    if (init==SDS_NOINIT)
        init = NULL;
    //如果没有初始化内容
    else if (!init)
        //将sh的前hdrlen+initlen+1个字节, 初始化成0
        memset(sh, 0, hdrlen+initlen+1);
    //计算出 sds 的指针, 也就是字符数组的指针
    s = (char*)sh+hdrlen;
    //计算出 flag 类型标识的指针
    fp = ((unsigned char*)s)-1;
    //重新计算sdsHdr已经使用的长度
    usable = usable-hdrlen-1;
    //如果可以长度超过sdsHdr类型的最大字符度长度, 则默认取对应类型的最大长度
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    //根据sdsHdr的类型设置 len, alloc, flag 属性
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
    }
    //如果有初始化长度且有初始化内容, 则将内容设置到字符数组中
    if (initlen && init)
        memcpy(s, init, initlen);
    //设置最后一个空串
    s[initlen] = '\0';
    //返回 sds
    return s;
}

//强制分配 sds 的内存
sds sdsnewlen(const void *init, size_t initlen) {
    return _sdsnewlen(init, initlen, 0);
}

//尝试分配 sds的内存
sds sdstrynewlen(const void *init, size_t initlen) {
    return _sdsnewlen(init, initlen, 1);
}

//创建一个空的sds
/* Create an empty (zero length) sds string. Even in this case the string
 * always has an implicit null term. */
sds sdsempty(void) {
    return sdsnewlen("",0);
}

//根据给定的C字符串创建sds
/* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}
  • _sdsnewlen()方法创建SDS的思路是, 根据字符串的长度, 计算出SDS类型, 然后申请sdsHdr的内存, 将内容设置到sdsHdr中, 最后返回sds指针

3.3 复制一个SDS

//复制一个sds
/* Duplicate an sds string. */
sds sdsdup(const sds s) {
    return sdsnewlen(s, sdslen(s));
}

3.4 释放SDS

//释放sds对象
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
    if (s == NULL) return;
    //计算出sdsHdr的指针, 然后释放内存
    s_free((char*)s-sdsHdrSize(s[-1]));
}

3.5 预分配SDS内存

// 1M = 1024 * 1024 * 1 byte
#define SDS_MAX_PREALLOC (1024*1024)

//检查sds长度是否足够添加对应长度的字符串, 不够的则预分配对应的长度
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    //获取sds的剩余空间
    size_t avail = sdsavail(s);
    size_t len, newlen;
    //获取sds的类型
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    //如果剩余空间够用, 则直接返回 sds
    if (avail >= addlen) return s;

    //往下走表示剩余空间不够用

    //获取sds字符串的长度
    len = sdslen(s);
    //计算出 sdsHdr 指针
    sh = (char*)s-sdsHdrSize(oldtype);
    //计算出字符串新的长度
    newlen = (len+addlen);
    //断言新的长度必须大于原有的长度, 避免溢出
    assert(newlen > len);   /* Catch size_t overflow */
    //如果新的长度小于 SDS_MAX_PREALLOC , 也就是小于 1M 时
    if (newlen < SDS_MAX_PREALLOC)
        //预分配2倍长度
        newlen *= 2;
    else
        //每次增加 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;

    //根据新的长度, 获取 sds 的类型
    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    //不使用 5 位, 最低使用 8 位
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    //获取类型对应的 sdsHdr 的内存大小
    hdrlen = sdsHdrSize(type);
    //断言避免溢出
    assert(hdrlen + newlen + 1 > len);  /* Catch size_t overflow */
    //如果sds类型不变
    if (oldtype==type) {
        //直接重分配, 扩展了空间
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        //分配不成功, 则返回 NULL
        if (newsh == NULL) return NULL;
        //设置sds的指针
        s = (char*)newsh+hdrlen;
    } else {
        //来到这里, 表示 sds 类型发生了变化
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        //分配新的sdsHdr
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        //分配失败返回 NULL
        if (newsh == NULL) return NULL;
        //将s的内容拷贝到新的sds中
        memcpy((char*)newsh+hdrlen, s, len+1);
        //释放原来的sds
        s_free(sh);
        //将s替换成新的sds
        s = (char*)newsh+hdrlen;
        //设置类型
        s[-1] = type;
        //设置长度
        sdssetlen(s, len);
    }
    //计算出剩余可用空间
    usable = usable-hdrlen-1;
    //如果剩余可以空间大于sds类型所能存的最大长度, 重置为sds类型的最大长度
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    //设置sds已分配的空间
    sdssetalloc(s, usable);
    //返回 sds
    return s;
}
  • 预分配内存, 首先检查sds剩余的内存空间是否足够存储新添加的长度, 足够则不处理
  • 不够存, 则根据现有的长度是否小于 SDS_MAX_PREALLOC, 小于则增加一倍, 大于等于则每次增加 SDS_MAX_PREALLOC
  • 然后根据新的字符串容量空间, 获取sds类型, 如果sds类型不变, 则调用realloc重分配
  • 如果sds类型发生了变化, 则根据新的类型申请的内存, 然后释放原来的内存, 最后设置sds内容和属性, 返回新的sds指针

3.6 回收SDS空闲的内存空间

//回收sds空闲的内存空间
sds sdsRemoveFreeSpace(sds s) {
    void *sh, *newsh;
    //获取sds的类型
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    //获取hdr结构体的大小
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
    //获取sds字符串的长度
    size_t len = sdslen(s);
    //获取sds剩余长度
    size_t avail = sdsavail(s);
    //获取sdsHdr旧指针
    sh = (char*)s-oldhdrlen;

    /* Return ASAP if there is no space left. */
    //如果没有剩余空间, 则不处理
    if (avail == 0) return s;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
    //根据字符串长度获取sds的新的类型
    type = sdsReqType(len);
    //根据新的类型获取sdsHdr的结构体大小
    hdrlen = sdsHdrSize(type);

    /* If the type is the same, or at least a large enough type is still
     * required, we just realloc(), letting the allocator to do the copy
     * only if really needed. Otherwise if the change is huge, we manually
     * reallocate the string to use the different header type. */
    //为什么类型大于 8 , 还是 realloc 呢, 这里有性能优化, realloc 相对 malloc 来说, realloc 更节省性能, realloc 只是将已分配的内存减少一下就行
    //如果结构体的类型不变, 或者 type 大于 SDS_TYPE_8
    if (oldtype==type || type > SDS_TYPE_8) {
        //直接在原指针上重分配内存, 只保留字符串长度的内存空间, 压缩了空间
        newsh = s_realloc(sh, oldhdrlen+len+1);
        //分配失败, 则返回 NULL
        if (newsh == NULL) return NULL;
        //返回新的sds指针
        s = (char*)newsh+oldhdrlen;
    } else {
        //只有变化很大的情况, 才去 malloc, 变化小都直接 realloc
        //oldtype != type && type == SDS_TYPE_8
        //根据新的类型长度分配内存
        newsh = s_malloc(hdrlen+len+1);
        //分配失败, 则返回 NULL
        if (newsh == NULL) return NULL;
        //将内容拷贝到新的内存中
        memcpy((char*)newsh+hdrlen, s, len+1);
        //释放原来的sdsHdr指针
        s_free(sh);
        //获取新的 sdsHdr 指针
        s = (char*)newsh+hdrlen;
        //设置 flag 属性
        s[-1] = type;
        //设置sds字符串长度
        sdssetlen(s, len);
    }
    //设置已分配容量大小
    sdssetalloc(s, len);
    //返回 sds
    return s;
}
  • 以现在的字符串长度, 获取sds类型, 然后判断类型是否变量和类型的值来决定是realloc分配还是malloc分配
  • 只有变化很大, 也就是类型由大变小并且是变成8, 才会进行申请新的内存.
  • 缩减内存, 默认上 realloc 会比 malloc 性能好很多
  • 在最苛刻的条件上, 才会重新申请内存, 是为了避免内存资源浪费

3.4 其他源码

其他源码比较简单, 有需要可以自行探讨, 基本都是对字符串的一些常用的操作


微信文章链接: https://mp.weixin.qq.com/s?__biz=MzI3NDAxNTAzMw==&mid=2247483716&idx=1&sn=b5cd16e246f8a473310f0bb1e72505be&chksm=eb1b3712dc6cbe04abcd4595c120ef70132189b3fdfa3f2f4ad04eadb4ac980c3c2610394298&token=122962709&lang=zh_CN#rd

个人微信: wolfleong

微信公众号: wolfleong

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容