一、SDS 1、SDS結構體 redis3.2之前 :不管buf的位元組數有多少,都用 4位元組的len來儲存長度 ,對於只存短字元串那麼優點 浪費空間 ,比如只存 ,則 則只需要一個位元組8位即可表示 redis3.2之後: \__attribute__ ((\__packed__)) 關鍵字是為了取消 ...
一、SDS
1、SDS結構體
redis3.2之前:不管buf的位元組數有多少,都用 4位元組的len來儲存長度,對於只存短字元串那麼優點浪費空間,比如只存 name
,則len=4
則只需要一個位元組8位即可表示
struct sdshdr {
unsigned int len; // buf中已占位元組數
unsigned int free; // buf中剩餘位元組數
char buf[]; // 數據空間
};
redis3.2之後:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; //已分配位元組數
uint8_t alloc; //剩餘位元組數
unsigned char flags; //標識屬於那種類型的SDS 低3存類型,高5不使用
char buf[];
};
//........16、32、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[]內,通過偏移來訪問
buf指針在char數組開頭位置,方便直接訪問
graph TB subgraph header-->buf end2、重要函數解析
sdsReqType
確定類型:sdsReqType根據傳入的 char[] 長度來缺點應該用哪種類型的 SDS結構體來描述
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
}
sdsnewlen
根據長度結構化 char數組,創建一個 長度為 str本身長度+head長度的數組, sdsnew就是調用這個來創建sds位元組數組的
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;
}
sdslen、sdsavail
返回使用和未使用的空間。 **根據頭部類型轉化指針,然後直接 sh->len 和 sh->alloc-sh->len **即可求出
sdscat、sdscatlen、sdsMakeRoomFor
將 t
拼接到 s
中,
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的核心代碼,會擴容大於需要的長度,防止多次擴容。體現了 預先分配
擴容是一個耗時的操作
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;
sdstrim
將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;
}
3、優點
A.獲取長度方便
c字元串獲取長度需要便利char數組,O(n),而SDS結構體記錄了長度,不需要char數組即可知道長度。
B.防止溢出
char數組不知道還有多少空間空餘,可能會在兩個字元串拼接的時候溢出,而SDS記錄了未使用的空間,可以有效的分配擴容,防止溢出。
C.記憶體分配方便和使用高效
傳統c的char數組,如果空間不足,需要手動擴容,然後複製原數據,截斷時,也需要縮減空間,來防止記憶體泄漏。但是SDS可以進行 空間預分配、惰性釋放 等策略來搞效的使用記憶體。
-
空間預分配:
預先分配足夠的空間,減少擴容次數
-
惰性釋放
因為SDS記錄了 free未分配空間欄位,所以截斷字元串的時候不需要立即複製元素進行縮減,直接增加 free 數值,減少 len即可,後面要增加字元串只增加len,減少free ,覆蓋寫入即可。(free = alloc-len)
D.相容C
SDS只是增加了兩個欄位,其實數據還是存在 char[] buf裡面的,所以可以使用 c內置的字元串處理函數來處理 SDS底層位元組數組。
typedef char *sds;
所以在處理 字元串的API里只是傳入了 char* 來處理字元串。空間是否充足都有額外的信息來描述。
二、鏈表
鏈表的話可以參考我的 https://www.cnblogs.com/biningooginind/p/12553163.html
基本參照了redis的鏈表操作。
1、結構體
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value; //void* 指針 可以存放任意類型的數據
} listNode;
2、特點
鏈表的特點:
- 刪除、插入 O(1)
- 遍歷訪問 O(n)
- 有head和tail指針,將訪問最後一個元素複雜度降低到O(1)
- 帶有 len長度,方便知道鏈表的長度
- 雙鏈表結構,前後遍歷都方便
- 無環
- 多態:數據用 void 來指向,可以存放任意類型數據,不用為每個類型都寫一個鏈表*
- 迭代器模式,鏈表有一個迭代器,方便遍歷節點
typedef struct listIter {
listNode *next; //下一個節點
int direction; //遍歷方向 forward or backward
} listIter;