redis3.2以前:無論buf的字節數有多少,都用 4字節的len來儲存長度,對於只存短字符串那麼優勢浪費空間,好比只存 name
,則len=4
則只須要一個字節8位便可表示html
struct sdshdr { unsigned int len; // buf中已佔字節數 unsigned int free; // buf中剩餘字節數 char buf[]; // 數據空間 };
redis3.2以後:redis
struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; //已分配字節數 uint8_t alloc; //剩餘字節數 unsigned char flags; //標識屬於那種類型的SDS 低3存類型,高5不使用 char buf[]; }; //........1六、3二、64
_attribute_ ((_packed_)) 關鍵字是爲了取消字節對齊數組
struct test1 { char c; int i; }; struct __attribute__ ((__packed__)) test2 { char c; int i; }; int main() { cout << "size of test1:" << sizeof(struct test1) << endl; cout << "size of test2:" << sizeof(struct test2) << endl; }
注意,這些結構都存在一個 char[]內,經過偏移來訪問curl
buf指針在char數組開頭位置,方便直接訪問函數
肯定類型:sdsReqType根據傳入的 char[] 長度來缺點應該用哪一種類型的 SDS結構體來描述ui
static inline char sdsReqType(size_t string_size) { if (string_size < 1<<5) return SDS_TYPE_5; if (string_size < 1<<8) //8位 表示長度範圍 0-256 return SDS_TYPE_8; if (string_size < 1<<16) //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 }
根據長度結構化 char數組,建立一個 長度爲 str自己長度+head長度的數組, sdsnew就是調用這個來建立sds字節數組的url
sds sdsnewlen(const void *init, size_t initlen) { void *sh; //存放sds header數據的頭指針 sds s; //char* s char type = sdsReqType(initlen); //根據str長度 肯定SDS header類型 if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); //header 長度 unsigned char *fp; //類型指針 sh = s_malloc(hdrlen+initlen+1); //分配 str長度+header長度的內存空間 ... memset(sh, 0, hdrlen+initlen+1); //初始化空間 s = (char*)sh+hdrlen; //移動到header以後的buf首地址位置 fp = ((unsigned char*)s)-1; //移動到header的尾部 標識sds header類型 switch(type) { .... case SDS_TYPE_8: { //#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); //sh指向header空間頭部位置 s表明buf首地址 下面將sh移動到header的首地址 SDS_HDR_VAR(8,s); //struct sdshdr8* sh = (void*)(s-sizeof(header)) sh->len = initlen; //填充數據 sh->alloc = initlen; *fp = type;//類型數據填充 break; } ...... } if (initlen && init) memcpy(s, init, initlen); //將str數據複製到buf中 s[initlen] = '\0'; return s; }
返回使用和未使用的空間。 **根據頭部類型轉化指針,而後直接 sh->len 和 sh->alloc-sh->len **便可求出指針
將 t
拼接到 s
中,code
sds sdscatsds(sds s, const sds t) { return sdscatlen(s, t, sdslen(t)); } sds sdscatlen(sds s, const void *t, size_t len) { size_t curlen = sdslen(s); s = sdsMakeRoomFor(s,len); //保證空間充足 if (s == NULL) return NULL; memcpy(s+curlen, t, len); //直接copy sdssetlen(s, curlen+len); //設置新的長度 s[curlen+len] = '\0'; return s; }
sdsMakeRoomFor
是爲了保證空間充足,若是不充足進行擴容,下面就是newlen的核心代碼,會擴容大於須要的長度,防止屢次擴容。體現了 預先分配htm
擴容是一個耗時的操做
if (avail >= addlen) return s; len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC) //#define SDS_MAX_PREALLOC (1024*1024) newlen *= 2; else newlen += SDS_MAX_PREALLOC;
將cset中在s出現的刪除,這個函數就體現了 惰性釋放 ,不會縮減空間,僅僅改變 len,同時也體現了 和 c的兼容性,能夠用 系統strings函數來操做 sds
sds sdstrim(sds s, const char *cset) { char *start, *end, *sp, *ep; size_t len; sp = start = s; ep = end = s+sdslen(s)-1; while(sp <= end && strchr(cset, *sp)) sp++; while(ep > sp && strchr(cset, *ep)) ep--; len = (sp > ep) ? 0 : ((ep-sp)+1); if (s != sp) memmove(s, sp, len); s[len] = '\0'; sdssetlen(s,len); return s; }
c字符串獲取長度須要便利char數組,O(n),而SDS結構體記錄了長度,不須要char數組便可知道長度。
char數組不知道還有多少空間空餘,可能會在兩個字符串拼接的時候溢出,而SDS記錄了未使用的空間,能夠有效的分配擴容,防止溢出。
傳統c的char數組,若是空間不足,須要手動擴容,而後複製原數據,截斷時,也須要縮減空間,來防止內存泄漏。可是SDS能夠進行 空間預分配、惰性釋放 等策略來搞效的使用內存。
空間預分配:
預先分配足夠的空間,減小擴容次數
惰性釋放
由於SDS記錄了 free未分配空間字段,因此截斷字符串的時候不須要當即複製元素進行縮減,直接增長 free 數值,減小 len便可,後面要增長字符串只增長len,減小free ,覆蓋寫入便可。(free = alloc-len)
SDS只是增長了兩個字段,其實數據仍是存在 char[] buf裏面的,因此可使用 c內置的字符串處理函數來處理 SDS底層字節數組。
typedef char *sds;
因此在處理 字符串的API裏只是傳入了 char* 來處理字符串。空間是否充足都有額外的信息來描述。
鏈表的話能夠參考個人 http://www.javashuo.com/article/p-pyvzjqrq-gk.html
基本參照了redis的鏈表操做。
typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; //void* 指針 能夠存聽任意類型的數據 } listNode;
鏈表的特色:
- 刪除、插入 O(1)
- 遍歷訪問 O(n)
typedef struct listIter { listNode *next; //下一個節點 int direction; //遍歷方向 forward or backward } listIter;