Redis基礎知識----SDS(簡單動態字串)
阿新 • • 發佈:2019-02-07
以下的總結,基於redis原始碼4.0.9版本
1、redis的底層儲存資料結構
簡單動態字串(SDS)、連結串列、字典、跳躍表、整樹集合、壓縮列表等
2、redis的資料結構物件
字串、列表、雜湊、集合、有序集合等
簡單動態字串(sds)
SDS通過字串的長短不同實現了5中不同的資料結構,其不同在於頭部用來標識字串長度的型別,主要用來減小結構體的大小,節省記憶體。
例如:長度小於32的字串,則會使用sdshdr5結構體,sdshdr5結構體特殊在並沒有len
和alloc
屬性,其巧妙的使用了flags
的高5位用來標識字串的長度。
sdshdr5 ----> <32
sdshdr8 ----> <256
sdshdr16 ----> < 2^16
sdshdr32 ----> < 2^32
sdshdr64 ----> < 2^64
基本資料結構如下:
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
排除header和terminator外的分配長度
flags
型別標識了結構體的實際型別
buf[]
真實資料
sdshdr5
資料結構
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
字串的擴容
/*
* 在必要情況下對SDS進行擴容
*
* 引數列表
* 1. s: 待擴容對SDS對字串指標
* 2. addlen: 需要新加入字串的長度
*
* 返回值
* 返回擴容後新的sds,如果沒擴容則和入參sds地址相同
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
// 首先計算出原SDS還剩多少可分配空間
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);
// 用sds(指向結構體尾部,字串首部)減去結構體長度得到結構體首部指標
// 結構體型別是不確定的,所以是void *sh
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
// 如果新長度小於最大預分配長度則分配擴容為2倍
// 如果新長度大於最大預分配長度則僅追加SDS_MAX_PREALLOC長度
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
// 字串的長度更改了,使用對頭部型別可能也會變化
type = sdsReqType(newlen);
// 由於SDS_TYPE_5沒有記錄剩餘空間(用多少分配多少),所以是不合適用來進行追加的
// 為了防止下次追加出現這種情況,所以直接分配SDS_TYPE_8型別
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 */
// 頭部型別有變化則重新開闢一塊記憶體並將原先整個SDS拷貝一份過去
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;
}
關鍵知識點:
1.SDS記錄長度,O(1)獲取整個長度
2.減少字串修改時記憶體分配次數。記憶體預分配,分配一定長度的字串,擴容時如果擴容需要的len小於SDS_MAX_PREALLOC,則擴大len的2倍,否則擴大SDS_MAX_PREALLOC(1M)(#define SDS_MAX_PREALLOC (1024*1024)
)。通過len和alloc實現惰性釋放,字串刪除後,長度不變,便於下次使用。
3.二進位制安全。不是靠空字元來判斷字串的結束的,而是通過len這個屬性。
4.杜絕快取去溢位。字串合併前,會檢查要當前字串的長度,不足,擴容。規則如 2.