通過上一篇文章无蜂,我們可以知道Redis字符串的底層數(shù)據(jù)結(jié)構(gòu)是SDS(Simple Dynamic String)
SDS定義
SDS的結(jié)構(gòu)定義可以從Redis安裝包src/sds.h中看到
Redis定義了五種長度的sds結(jié)構(gòu),基本重要屬性:
len:已使用長度线欲,也就是當(dāng)前字符串已使用內(nèi)存大小
alloc:總分配的內(nèi)存大小造烁,可以理解為buf[]的長度
flag:第三位表示當(dāng)前sds的類型否过,高五位沒用
buf[]:字節(jié)數(shù)組,用于保存字符串惭蟋,字節(jié)\0結(jié)尾苗桂,占用1個(gè)字節(jié)
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
從注釋可以看出,sdshdr5這個(gè)數(shù)據(jù)結(jié)構(gòu)沒有使用告组。sdshdr5只是對于key/value中煤伟,value為字符串時(shí)沒有使用,當(dāng)key很小時(shí)木缝,還是有用便锨。sdshdr5字符串的長度適用于長度小于32位的字符串(1<<5,即2^5=32)我碟,且少了兩個(gè)關(guān)鍵字段放案,不利于動態(tài)擴(kuò)容,當(dāng)于預(yù)分配的內(nèi)存用完時(shí)矫俺,就需要重新分配內(nèi)存且進(jìn)行數(shù)據(jù)復(fù)制遷移吱殉,對性能影響比較大〉г現(xiàn)在對于value,最小的字符串都是用sdshdr8開始存儲考婴。
SDS與C語言字符串的區(qū)別
sds相比于C語言原聲字符串的優(yōu)勢:
- 記錄了長度信息贩虾,獲取字符串長度信息的時(shí)間復(fù)雜度為O(1)催烘,c語言獲取字符串長度每次都需要便利字節(jié)數(shù)組沥阱,時(shí)間復(fù)雜度為O(N)。
- c語言對字符串拼接伊群,每次都需要重新分配內(nèi)存并進(jìn)行數(shù)據(jù)遷移考杉。sds有預(yù)分配內(nèi)存,當(dāng)內(nèi)存足夠時(shí)舰始,不需要進(jìn)行內(nèi)存的重新分配和數(shù)據(jù)復(fù)制遷移崇棠。
- sds還是保持了\0結(jié)尾,這樣可以直接調(diào)用c語言的原聲方法丸卷。
sds空間預(yù)分配:
對sds修改后枕稀,sds的長度小于1MB,那么Redis會分配一個(gè)同樣大小未使用空間谜嫉,即sds的len和未使用空間是一樣的萎坷。比如修改,sds的len變?yōu)榱?0個(gè)字節(jié)沐兰,那么sds的總長度會變成20個(gè)字節(jié)哆档。
對sds修改后,sds的長度大于1MB住闯,每次擴(kuò)容都會以1MB的長度擴(kuò)容瓜浸。比如修改后sds的長度為2MB,那么分配后的總長度為3MB比原,未使用長度是1MB插佛。
sds惰性釋放:
對字符串進(jìn)行縮減操作,Redis不會立馬釋放內(nèi)存量窘,而是繼續(xù)空在那里朗涩,等待以后使用。
字符串編碼
由上一節(jié)我們知道绑改,每個(gè)對象谢床,除了有數(shù)據(jù)結(jié)構(gòu)對象外,還有一個(gè)編碼類型厘线,字符串對象有三種編碼類型:int识腿、raw、embstr造壮。如果一個(gè)字符串對象保存的是整數(shù)值渡讼,并且這個(gè)整數(shù)值可以用一個(gè)long類型來表示骂束,那么字符串對象就會將整數(shù)值保存在字符串結(jié)構(gòu)中的*ptr屬性里面,并且將編碼類型設(shè)置為int成箫。
? [圖片上傳失敗...(image-49f796-1604411712176)]
如果一個(gè)字符串對象保存的是一個(gè)字符串展箱,并且改字符串的長度大于44字節(jié),那么字符串對象就會用SDS來保存字符串蹬昌,并且將編碼設(shè)置為raw類型
如果一個(gè)字符串對象保存的是一個(gè)字符串混驰,并且改字符串的長度小于等于44字節(jié),那么字符串對象就會用SDS來保存字符串皂贩,并且將編碼設(shè)置為embstr類型
embstr是一種專門用來保存短字符串的編碼方式栖榨,它只會調(diào)用一次內(nèi)存分配函數(shù)來分配一塊連續(xù)的內(nèi)存用來保存redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu)。而raw會調(diào)用兩次內(nèi)存分配函數(shù)來分別分配redisObject結(jié)構(gòu)內(nèi)存和shshdr內(nèi)存明刷。
為什么是44個(gè)字節(jié)
我們看下一個(gè)redisObject最小有多少個(gè)字節(jié)
typedf struct RedisObject {
int4 type; // 4bits
int4 encoding; // 4bits
int24 lru; // 24bits 3byte
int32 refcount; // 4byte
void *ptr; // 8byte婴栽,64-bit system
} robj;
4bit+4bit+24bit+32bit+64bit=128bit=16byte
*ptr指向的是一個(gè)sds結(jié)構(gòu),sds的基本結(jié)構(gòu)如下:
struct SDS{
int8 len; //1byte
int8 alloc; //1byte
int8 flags; //1byte
char buf[]; //內(nèi)聯(lián)數(shù)組辈末,長度為capacity
}
一個(gè)sds結(jié)構(gòu)對象頭的大小是capacity+3
這就意味著一個(gè)字符串對象的最下空間是16+3=19個(gè)字節(jié)(capacity為0)
內(nèi)存分配函數(shù)分配內(nèi)存的大小單位一般是2愚争、4、8挤聘、16轰枝、32、64等字節(jié)長度檬洞。所以為了容納一個(gè)完成的字符串對象狸膏,malloc函數(shù)最少會分配一個(gè)32字節(jié)的內(nèi)存,當(dāng)字符串稍微長一點(diǎn)的時(shí)候添怔,就會分配64字節(jié)的空間湾戳。當(dāng)字符串對象總體超過64字節(jié)時(shí),Redis會認(rèn)為這個(gè)字符串對象是一個(gè)大字符串广料,會用raw的形式存儲砾脑。
一個(gè)字符串對象總體如果為64字節(jié),那么能夠存儲的字符串的長度就是64-19=45字節(jié)艾杏,字符串是以字節(jié)\0結(jié)尾韧衣,占用一個(gè)字節(jié),64字節(jié)的字符串對象购桑,能容納的字符串就是44字節(jié)畅铭。這就是為什么當(dāng)字符串長度超過44字節(jié)時(shí),Redis才用raw的形式存儲字符串勃蜘。
文章持續(xù)更新硕噩,歡迎大家關(guān)注我公眾號,微信搜索「易大師的小屋」缭贡。