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 其他源码
其他源码比较简单, 有需要可以自行探讨, 基本都是对字符串的一些常用的操作
个人微信: wolfleong
微信公众号: wolfleong