Redis是一個Key Value資料庫。Redis有5種數據類型:字元串、列表、哈希、集合、有序集合。而字元串的底層實現方法之一就是使用sds。以下描述中請讀者註意區分sds是指簡單動態字元串這一數據結構(用大寫表示)還是sdshdr頭部中buf數組的起始地址(用小寫表示)。 SDS源碼 如下源碼 ...
Redis是一個Key Value資料庫。Redis有5種數據類型:字元串、列表、哈希、集合、有序集合。而字元串的底層實現方法之一就是使用sds。以下描述中請讀者註意區分sds是指簡單動態字元串這一數據結構(用大寫表示)還是sdshdr頭部中buf數組的起始地址(用小寫表示)。
SDS源碼
如下源碼所示。
根據要保存的字元串長度選用不同的頭部大小,從而節省記憶體,註意sdshdr5與其他不同,下麵會有介紹。
SDS由兩部分組成:sds、sdshdr。sds是一個char類型的指針,指向buf數組首元素,buf數組是存儲字元串的實際位置;sdshdr是SDS的頭部,為SDS加上一個頭部的好處就是為了提高某些地方的效率,比如獲取buf數組中字元串長度,用O(1)的複雜度從頭部就能取得。buf數組是一個空數組,從而使得sdshdr是一個可變長度的結構體,用一個空數組的好處就是分配記憶體時,只用分配一次,而且頭部所占用的記憶體和sds的記憶體是連續的,釋放時也只用釋放一次。
sdshdr結構體中各欄位的介紹:len : 已存儲的字元串長度;alloc : 能存儲的字元串的最大容量,不包括SDS頭部和結尾的NULL字元;flags : 標誌位,低3位代表了sds頭部類型,高5位未用;buf[] : 字元數組,存儲字元串;註意sdshdr5沒有len和alloc欄位,其flags的低3位同樣代表頭部類型,但高5位代表保存的字元串長度。 __attribute__ ((__packed__)) : 使得編譯器不會因為記憶體對齊而在結構體中填充位元組,以保證記憶體的緊湊,這樣sds - 1就可以得到flags欄位,進而能夠得到其頭部類型。如果填充了位元組,則就不能得到flags欄位。
buf數組尾部隱含有一個'\0',SDS是以len欄位來判斷是否到達字元串末尾,而不是以'\0'判斷結尾。所以sds存儲的字元串中間可以出現'\0',即sds字元串是二進位安全的。
typedef char *sds; struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; 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[]; }; 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[]; }; 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[]; }; 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的低三位代表不同類型的sds頭部: #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 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3
SDS操作
因為sds和頭部是記憶體連續的,所以當我們得到了一個sds,只要將它-1就可得到flags欄位,減頭部大小即可得到頭部起始地址。SDS的很多操作就是利用了這一點,從而帶來了極大的方便和快速。下麵我們介紹幾個SDS比較重要的幾個操作
1. 獲取頭部起始地址
將sds減去頭部大小即可。非常方便快速。
// 返回一個指向sds頭部的起始地址的指針 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); // 返回sds頭部的起始地址 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
2. 獲取buf數組中sds存儲的字元串長度
先後移1位,得到flags欄位,再和掩碼相與即可得到頭部類型。
static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; // 記憶體空間連續,所以往後移1個位元組,便是flags欄位 switch(flags&SDS_TYPE_MASK) { // 和flags低3位相與,得到sds頭部類型 case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->len; // 先移動到sds頭部的起始地址,進而可以直接獲取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; }
3. 獲取buf數組中剩餘可用的記憶體大小
static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; // 後移1位元組,得到flags欄位 switch(flags&SDS_TYPE_MASK) { // 得到sds頭部類型 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; }
4. 使用字元串初始化一個SDS
註意分配時,程式會自動為buf數組最後一個元素後面添加上'\0','\0'對外部完全是透明的,分配記憶體時自動多分配1個位元組保存'\0',buf數組最後自動添加'\0'。

// sds尾部隱含有一個'\0';sds是以len欄位來判斷是否到達字元串末尾 // 所以sds存儲的字元串中間可以出現'\0',即sds字元串是二進位安全的 // 分配一個新sds,buf數組存儲內容init sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); // 根據長度大小選擇合適的sds頭部 /* 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); // 獲取sds頭部大小 unsigned char *fp; /* flags pointer. */ // 為sds分配記憶體,總大小為:頭部大小+存儲字元串的長度+末尾隱含的空字元大小 sh = s_malloc(hdrlen+initlen+1); if (!init) memset(sh, 0, hdrlen+initlen+1); // 記憶體初始化為0 if (sh == NULL) return NULL; s = (char*)sh+hdrlen; // buf數組的起始地址 fp = ((unsigned char*)s)-1; // 指向flags欄位 // 初始化sds頭部的len,alloc,flags欄位 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; } } // 初始化buf數組 if (initlen && init) memcpy(s, init, initlen); // 拷貝init到buf數組 s[initlen] = '\0'; // 添加末尾的空字元 return s; }View Code
5. 空間預分配
當需要將SDS的len增加addlen個位元組時,如果SDS剩餘空間足夠,則什麼都不用做。如果剩餘空間不夠,則會分配新的記憶體空間,並且採用預分配。新長度newlen為原len+addlen,若newlen小於1M,則為SDS分配新的記憶體大小為2*newlen;若newlen大於等於1M,則SDS分配新的記憶體大小為newlen + 1M。

// 為sds的len欄位增加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; // sds剩餘空間足夠 len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); // sds剩餘空間不夠,新的len為len+addlen // 下麵兩步實現空間預分配 if (newlen < SDS_MAX_PREALLOC) // 新長度小於1M,則len設為2*(len+addlen)大小 newlen *= 2; else newlen += SDS_MAX_PREALLOC; // 新長度大於1M,則len設為 len+1M 大小 type = sdsReqType(newlen); // 新len對應的sds頭部 /* 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; }View Code
6. 惰性空間釋放
當要清空一個SDS時,並不真正釋放其記憶體,而是設置len欄位為0即可,這樣當之後再次使用到該SDS時,可避免重新分配記憶體,從而提高效率。
// 清空sds內容,len欄位清為0 // 但之前的空間並未釋放,可避免以後的重新分配記憶體。實現惰性空間釋放 void sdsclear(sds s) { sdssetlen(s, 0); s[0] = '\0'; }
只要理解了sds和sdshdr,其操作函數便很容易理解。剩下的就不一一介紹了,我在閱讀過程中也做了部分註釋,下麵附上源碼及註釋。SDS共兩個文件:sds.h和sds.c
sds.h :

/* SDSLib 2.0 -- A C dynamic strings library * 簡單動態字元串 */ #ifndef __SDS_H #define __SDS_H #define SDS_MAX_PREALLOC (1024*1024) // 1M,空間預分配使用 #include <sys/types.h> #include <stdarg.h> #include <stdint.h> // 指向存儲數據的起始地址 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由兩部分組成:sds頭部(即下麵的各種結構體)、真正存儲字元串的字元數組 // 這兩部分在記憶體上連續 // len : 已存儲的字元串長度 // alloc : 能存儲的字元串的最大容量,不包括sds頭部和結尾的NULL字元 // flags : 標誌位,最低三位代表了sds頭部類型 // buf[] : 字元數組,存儲字元串 // __attribute__ ((__packed__)) : // 使得編譯器不會因為記憶體對齊而在結構體中填充位元組,以保證記憶體的緊湊,使得下麵的s[-1]得到正確的地址 // char buf[] : 初始時不占用記憶體,而且使得頭部記憶體和存儲字元串的記憶體地址連續。 // sdshdr5比較特殊,flags欄位低3位代表sds頭部類型,高5位代表已存儲的字元串長度 // 分為不同類型的頭部,目的是為了存儲不同長度的字元串使用不同類型,從而節省記憶體 struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; 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[]; }; 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[]; }; 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[]; }; 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的低三位代表不同類型的sds頭部: #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 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3 // 返回一個指向sds頭部的起始地址的指針 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); // 返回sds頭部的起始地址 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) // 獲取sdshdr5類型的sds存儲的字元串長度 #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 獲取buf數組中sds存儲的字元串長度 static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; // 記憶體空間連續,所以往後移1個位元組,便是flags欄位 switch(flags&SDS_TYPE_MASK) { // 和flags低3位相與,得到sds頭部類型 case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->len; // 先移動到sds頭部的起始地址,進而可以直接獲取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; } // 獲取buf數組中剩餘可用的記憶體大小 static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; // 後移1位元組,得到flags欄位 switch(flags&SDS_TYPE_MASK) { // 得到sds頭部類型 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頭部的len欄位 static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { // 對於sdshdr5,則是設置flags的高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頭部的len欄位增加inc static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { // 對於sdshdr5,則是設置flags的高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() */ // 獲取sds的buf數組總的大小 static inline size_t sdsalloc(const sds s) { 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的buf數組總的大小 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 sdsnewlen(const void *init, size_t initlen); sds sdsnew(const char *init); sds sdsempty(void); sds sdsdup(const sds s); void sdsfree(sds s); sds sdsgrowzero(sds s, size_t len); sds sdscatlen(sds s, const void *t, size_t len); sds sdscat(sds s, const char *t); sds sdscatsds(sds s, const sds t); sds sdscpylen(sds s, const char *t, size_t len); sds sdscpy(sds s, const char *t); 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 sdstrim(sds s, const char *cset); void sdsrange(sds s, int start, int end); void sdsupdatelen(sds s); void sdsclear(sds s); int sdscmp(const sds s1, const sds s2); sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); void sdsfreesplitres(sds *tokens, int count); void sdstolower(sds s); void sdstoupper(sds s); sds sdsfromlonglong(long long value); sds sdscatrepr(sds s, const char *p, size_t len); sds *sdssplitargs(const char *line, int *argc); sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); sds sdsjoin(char **argv, int argc, char *sep); sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); /* Low level functions exposed to the user API */ sds sdsMakeRoomFor(sds s, size_t addlen); void sdsIncrLen(sds s, int incr); sds sdsRemoveFreeSpace(sds s); size_t sdsAllocSize(sds s); 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. */ void *sds_malloc(size_t size); void *sds_realloc(void *ptr, size_t size); void sds_free(void *ptr); #ifdef REDIS_TEST int sdsTest(int argc, char *argv[]); #endif #endifView Code
sds.c :

/* SDSLib 2.0 -- A C dynamic strings library */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <assert.h> #include "sds.h" #include "sdsalloc.h" // 獲取type類型的sds對應的頭部類型大小 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頭部 static inline char sdsReqType(size_t string_size) { if (string_size < 1<<5) // 0~2^5-1 return SDS_TYPE_5; if (string_size < 1<<8) // 2^5~2^8-1 return SDS_TYPE_8; if (string_size < 1<<16) // 2^8~2^16-1 return SDS_TYPE_16; if (string_size < 1<<32) // 2^16~2^32-1 return SDS_TYPE_32; return SDS_TYPE_64; // 2^32~ } /* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-termined (all the sds strings are, always) so * even if you create an sds string with: * * mystring = sdsnewlen("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain * \0 characters in the middle, as the length is stored in the sds header. */ // sds尾部隱含有一個'\0';sds是以len欄位來判斷是否到達字元串末尾 // 所以sds存儲的字元串中間可以出現'\0',即sds字元串是二進位安全的 // 分配一個新sds,buf數組存儲內容init sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); // 根據長度大小選擇合適的sds頭部 /* 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); // 獲取sds頭部大小 unsigned char *fp; /* flags pointer. */ // 為sds分配記憶體,總大小為:頭部大小+存儲字元串的長度+末尾隱含的空字元大小 sh = s_malloc(hdrlen+initlen+1); if (!init) memset(sh, 0, hdrlen+initlen+1); // 記憶體初始化為0 if (sh == NULL) return NULL; s = (char*)sh+hdrlen; // buf數組的起始地址 fp = ((unsigned char*)s)-1; // 指向flags欄位 // 初始化sds頭部的len,alloc,flags欄位 switch(type) { case SDS_TYPE_5: { *fp = type | (initlen << SDS_TYPE_BITS); break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); sh