redis支持多種數據類型,sds(simple dynamic string)是最基本的一種,redis中的字符串類型大多使用sds保存,它支持動態的擴展與壓縮,並提供許多工具函數。這篇文章將分析sds在redis中是如何實現的。redis
sds在redis中其實就是一個char*類型的別名,聲明以下:app
typedef char *sds;
可是,以sds指向的字符串的存儲格式具備必定的規則,即在字符串數據以前存儲了相應的頭部信息,這些頭部信息包含了:1. alloc-分配的內存空間長度。2. len-有效字符串長度。3. flags-頭部類型。函數
redis中有sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64這5類頭部類型,其聲明以下:工具
struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; };
其它幾種類型相似,但len與alloc字段根據頭部類型使用不一樣的類型,sdshdr16使用uint16_t,sdshdr32使用uint32_t,sdshdr64使用uint64_t。另外,sdshdr5與其它頭部類型不一樣,沒有len與alloc字段,並將字符串實際長度保存在flags字段的高5bits。ui
sdshdr8因爲alloc爲uint8_t類型,所以能夠表示的字符串最長爲255字節;sdshdr16因爲alloc爲uint16_t類型,所以能夠表示的字符串最長爲65536字節;相似的,redis中選擇最合適的頭部去存儲字符串,節約少量空間(我認爲當字符串較短時,使用sdshdr8能夠節約空間,但當字符串長度超過了uint8_t的表示範圍,使用sdshdr16與sdshdr32,頭部長度佔所用空間的比例差異不大)。選擇該使用何種頭部的函數實現以下:this
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 }
sds的類型指向有效的字符串起始位置,頭部信息與有效字符串的存儲空間是統一分配的,它們的內存空間的連續的,所以將sds向前移動頭部長度,便可獲得實際分配的內存起始地址。而sds頭部長度由頭部類型決定,其實現以下:spa
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; }
sds的頭部類型保存在頭部的flags的低3bits,頭部類型能夠經過以下方式得到code
oldtype = s[-1] & SDS_TYPE_MASK; //s爲sds類型
實際分配內存地址由以下方式得到blog
sh = (char*)s-sdsHdrSize(oldtype);
所此,sdsfree操做實現以下:內存
void sdsfree(sds s) { if (s == NULL) return; s_free((char*)s-sdsHdrSize(s[-1])); }
新建sds類型的函數聲明爲:
sds sdsnewlen(const void *init, size_t initlen)
它的大體步驟可描述以下:
函數具體實現以下:
sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ sh = s_malloc(hdrlen+initlen+1); if (init==SDS_NOINIT) init = NULL; else if (!init) memset(sh, 0, hdrlen+initlen+1); if (sh == NULL) return NULL; s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; 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 = initlen; *fp = type; break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } if (initlen && init) memcpy(s, init, initlen); s[initlen] = '\0'; return s; }
爲已存在sds類型增長存儲空間的操做函數聲明爲:
sds sdsMakeRoomFor(sds s, size_t addlen)
它的操做步驟大體以下:
函數具體實現以下:
sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; size_t avail = sdsavail(s); size_t len, newlen; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; 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. */ if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ newsh = s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, newlen); return s; }
sds頭部alloc記錄了分配的內存空間,len記錄了實際使用的內存空間,當須要釋放未使用的內存空間時,根據len記錄,可使用s_realloc壓縮空間或者使用s_alloc從新分配更小的空間,釋放舊的內存。相應函數聲明爲:
sds sdsRemoveFreeSpace(sds s)
大體操做步驟以下:
sdsRemoveFreeSpace的實現與sds sdsMakeRoomFor大體相同,此處再也不列出。
另外sds還提供了不少工具函數,如cat操做,copy操做,ll2str(整數轉字符串),vprintf操做(格式化操做)等。具體實現見源碼文件sds.c。