1 SDS的介紹
Redis 沒有直接使用C原生的字符串, 而是自己構建了一種簡單動態(tài)字符串(simple dynamic string), 簡稱SDS. redis只有在一些無須對字符串進行修改的地方, 如打印日志, 才會用原生的C字符串.
sds的優(yōu)化點:
| c 字符串 | 原因 | SDS | 優(yōu)化 |
|---|---|---|---|
| 獲取字符串長度復雜度為O(N) | 沒有記錄字符串長度, 需要遍歷數組 | 獲取字符串長度復雜度為O(1) | 記錄字符串的長度 |
| API是不安全的, 可能會造成緩沖區(qū)溢出 | 字符數組沒有長度 | API是安全的, 不會造成緩沖區(qū)溢出 | SDS 空間分配策略會自動擴展數組的長度 |
| 修改字符串長度N次必然會執(zhí)行N次內存重分配 | 字符串就是字符數組, 增加字符需要重分配空間, 減少字符需要縮減空間 | 修改字符串長度N次最多需要執(zhí)行N次內存重分配 | 1. 空間預分配 2. 惰性空間釋放 |
| 只能保存文本數據 | 字符數組以'\0'為字符串結束符 | 可以保存文本和二進制數據 | 以len長度為是否結束 |
| 可以使用所有的<string.h>庫中的函數 | 原C生字符串肯定可以用原生的庫 | 可以使用所有的<string.h>庫中一部分的函數 | sds也會在字符最后添加字符串結束符 |
2 sds.h 源碼解析
2.1 SDS 結構體
redis4.0之后, 開始使用5種header結構體來定義SDS, redis會自動根據字符串的不同的長度選擇不同的結構體來保存字符串, 從而節(jié)省內存的使用.
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)
//內聯(lián)函數, 用于獲取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;
}
//內聯(lián)函數, 用于獲取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;
}
}
//創(chuàng)建給定長度的 sds
sds sdsnewlen(const void *init, size_t initlen);
sds sdstrynewlen(const void *init, size_t initlen);
//創(chuàng)建字符串數組創(chuàng)建 sds
sds sdsnew(const char *init);
//創(chuàng)建空在的 sds
sds sdsempty(void);
//復制sds對象返回
sds sdsdup(const sds s);
//釋放sds
void sdsfree(sds s);
//用空字符串擴展sds的長度
sds sdsgrowzero(sds s, size_t len);
//將二進制安全的字符串附加到現(xiàn)有的 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字符串中出現(xiàn)過的字符
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 生成的動態(tài)數組的內存
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 中出現(xiàn)的字符替換成 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 創(chuàng)建新SDS字符串相關方法
//用于標識, 創(chuàng)建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個字節(jié), 初始化成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);
}
//創(chuàng)建一個空的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字符串創(chuàng)建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()方法創(chuàng)建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 類型發(fā)生了變化
/* 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剩余的內存空間是否足夠存儲新添加的長度, 足夠則不處理
- 不夠存, 則根據現(xiàn)有的長度是否小于 SDS_MAX_PREALLOC, 小于則增加一倍, 大于等于則每次增加 SDS_MAX_PREALLOC
- 然后根據新的字符串容量空間, 獲取sds類型, 如果sds類型不變, 則調用realloc重分配
- 如果sds類型發(fā)生了變化, 則根據新的類型申請的內存, 然后釋放原來的內存, 最后設置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 呢, 這里有性能優(yōu)化, realloc 相對 malloc 來說, realloc 更節(jié)省性能, 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;
}
- 以現(xiàn)在的字符串長度, 獲取sds類型, 然后判斷類型是否變量和類型的值來決定是realloc分配還是malloc分配
- 只有變化很大, 也就是類型由大變小并且是變成8, 才會進行申請新的內存.
- 縮減內存, 默認上 realloc 會比 malloc 性能好很多
- 在最苛刻的條件上, 才會重新申請內存, 是為了避免內存資源浪費
3.4 其他源碼
其他源碼比較簡單, 有需要可以自行探討, 基本都是對字符串的一些常用的操作
個人微信: wolfleong
微信公眾號: wolfleong