咱們在redis中執行命令redis
set key name
的時候,key和name都是字符串類型,並且字符串(string)在redis中是會常常用到的類型,那redis是如何保存字符串的呢?咱們接下來往下看
衆所周知,redis是c寫的,在c中使用char來保存字符串,而且用\0做爲字符串的結尾,可是redis不是這樣保存的,redis是使用一種叫SDS的結構來保存字符串的。結構以下(redis3.2之前)數據庫
struct sdshdr{ int len; int free; char buf[]; }
那麼問題來了,redis爲何 會用SDS的結構,而不直接用c語言的字符串,咱們來看看他們的區別緩存
對於c來講,計算字符串的長度的方式就是遍歷,遇到\0就中止,因此複雜對是O(n),而SDS直接保存了字符串的長度,複雜度是O(1)安全
由於SDS並非以\0爲結尾的標誌,天然就保證了二進制的安全微信
redis是一個高速的緩存數據庫,須要頻繁的對字符串進行操做,若是內存分配錯誤,會致使很嚴重的後果,就算內存分配沒問題,頻繁的內存分配也是很是耗費時間的,因此這些都是應該去避免的函數
在SDS中首先用到了惰性空間釋放策略,惰性空間釋放用於優化SDS的字符串縮短操做。
當要縮短SDS保存的字符串時,程序並不當即使用內存充分配來回收縮短後多出來的字節,而是使用表頭的free成員將這些字節記錄起來,並等待未來使用。
源碼以下優化
void sdsclear(sds s) { //重置sds的buf空間,懶惰釋放 struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); sh->free += sh->len; //表頭free成員+已使用空間的長度len = 新的free sh->len = 0; //已使用空間變爲0 sh->buf[0] = '\0'; //字符串置空 }
擴容策略是字符串在長度小於 1M 以前,擴容空間採用加倍策略,也就是保留 100% 的冗餘空間。當長度超過1M 以後,爲了不加倍後的冗餘空間過大而致使浪費,每次擴容只會多分配 1M大小的冗餘空間。ui
前面的len和free以及char這種結構看起來很好,可是是存在必定的問題的3d
struct sdshdr{ int len; int free; char buf[]; }
len和free都是int類型,都是4byte也就是32bit,能表示42億左右的範圍,大大的形成了空間的浪費,因此在3.2之後對SDS有必定的更改,更改以下code
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. */ 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[]; }; .........
sdshdr5表示的是用5個bit位來表示數據的長度,sdshdr8就是表示用8個bit位來表示數據的長度,以此類推
sdshdr5的內存分配如圖
當須要存儲的數據長度超過31,就須要用sdshdr8來表示
sdshdr8的內存分配如圖
其他的sdshdr16以上的都是以此類推,判斷方式源碼以下
static inline char sdsReqType(size_t string_size) { if (string_size < 1<<5) //2^5-1 return SDS_TYPE_5; if (string_size < 1<<8) //2^8-1 return SDS_TYPE_8; if (string_size < 1<<16) //2^16-1 return SDS_TYPE_16; #if (LONG_MAX == LLONG_MAX) if (string_size < 1ll<<32) //2^32-1 return SDS_TYPE_32; return SDS_TYPE_64; #else return SDS_TYPE_32; #endif }
關注個人技術公衆號,每週都有優質技術文章推送。
微信掃一掃下方二維碼便可關注: