標籤:
zipmap
在hashtable實現中,Redis引入了zipmap資料結構,保證在hashtable剛建立以及元素較少時,用更少的記憶體來儲存,同時對查詢的效率也不會受太大的影響。
zipmap利用字串實現了簡單的hash表,來儲存少量key-value對。
記憶體布局
zipmap的記憶體布局如下:
1)zmlen:1個位元組 ,記錄當前zipmap中key-value對的數量。由於zmlen只有1個位元組,因此規定其表示的數量只能為0~254,當zmlen>254時,就需要遍曆整個zipmap來得到key-value對的個數。
2)len: 用於記錄key或value的長度,有兩種情況,當len的第一個位元組為0~253時,那麼len就只佔用這一個位元組。的那個len的第一個位元組為254時,那麼len將用後面的4個位元組來表示。因此len要麼佔用1位元組,要麼佔用5位元組。
3)free:1位元組 ,表示隨後的value後面的空閑位元組數,這主要是改變key的value引起的,如將”foo” => “bar”變為”foo” => “hi”,那麼會導致1個位元組的空閑空間。當free的位元組數過大用1個位元組不足以表示時,zipmap就會重新分配記憶體,保證字串盡量緊湊。
4)end:1個位元組 ,為0xFF,用於標誌zipmap的結束
一個簡單的樣本如下:
“\x02\x03foo\x03\x00bar\x05hello\x05\x00world\xff”
可以發現:zmlen=2 key1_len=3 key1=”foo” value1_len=3 free_len=0 value1=”bar”
key2_len=5 key2=”hello” value2_len=5 free_len=0 value2=”world” end=0xff
即當前zipmap中共有兩個key-value對,分別為 “foo” => “bar” 和 “hello” => “world”
建立zipmap
unsigned char *zipmapNew(void) { unsigned char *zm = zmalloc(2); zm[0] = 0; /* Length */ zm[1] = ZIPMAP_END; return zm;}
對於一個空的zipmap只有2個位元組,1個位元組的zmlen=0,1一個位元組的end=0xFF
尋找
//在zm中尋找key,當totlen=NULL時,表示不需要得到整個zipmap佔用的位元組數static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) { unsigned char *p = zm+1, *k = NULL; unsigned int l,llen; while(*p != ZIPMAP_END) { unsigned char free; l = zipmapDecodeLength(p); //得到key_len llen = zipmapEncodeLength(NULL,l); //得到key_len佔用的位元組數 if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) { if (totlen != NULL) { k = p; } else { //不需要zipmap的位元組數,直接返回 return p; } } //跳過當前key-value對,比較下一個 p += llen+l; l = zipmapDecodeLength(p); p += zipmapEncodeLength(NULL,l); free = p[0]; p += l+1+free; /* +1 to skip the free byte */ } //否則用totlen記錄zipmap佔用的位元組數 if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1; return k; }
set操作
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) { unsigned int zmlen, offset; unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen); unsigned int empty, vempty; unsigned char *p; freelen = reqlen; if (update) *update = 0; p = zipmapLookupRaw(zm,key,klen,&zmlen); //首先在zipmap中尋找key if (p == NULL) { //當zipmap中不存在key時,擴充記憶體 zm = zipmapResize(zm, zmlen+reqlen); p = zm+zmlen-1; zmlen = zmlen+reqlen; if (zm[0] < ZIPMAP_BIGLEN) zm[0]++; } else { //zipmap中已有key,則需要將其value更新為val if (update) *update = 1; freelen = zipmapRawEntryLength(p); if (freelen < reqlen) { //如果空閑空間不足時,需要擴充記憶體 offset = p-zm; zm = zipmapResize(zm, zmlen-freelen+reqlen); p = zm+offset; memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1)); zmlen = zmlen-freelen+reqlen; freelen = reqlen; } } //將當前key-value對後面的內容向後移動,預留空間 empty = freelen-reqlen; if (empty >= ZIPMAP_VALUE_MAX_FREE) { offset = p-zm; memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1)); zmlen -= empty; zm = zipmapResize(zm, zmlen); p = zm+offset; vempty = 0; } else { vempty = empty; } //向zipmap中寫入key-value對 p += zipmapEncodeLength(p,klen); memcpy(p,key,klen); p += klen; p += zipmapEncodeLength(p,vlen); *p++ = vempty; memcpy(p,val,vlen); return zm;}
get操作
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen) { unsigned char *p; if ((p = zipmapLookupRaw(zm,key,klen,NULL)) == NULL) return 0; p += zipmapRawKeyLength(p); *vlen = zipmapDecodeLength(p); *value = p + ZIPMAP_LEN_BYTES(*vlen) + 1; return 1;}
返回1時表示尋找成功。當尋找成功時,將value的地址和value_len分別儲存在value和vlen中返回。
本文所引用的源碼全部來自Redis3.0.7版本
redis學習參考資料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 設計與實現(第二版)
http://blog.csdn.net/xiejingfa/article/details/51111230
redis學習筆記(7)---壓縮字典zipmap