Redis數據結構-字典
字典是比較麻煩的一種數據結構,數據結構也相對複雜一些,redis2-x和redis4-x版本該數據結構定義是一致的,本文拿redis4-x來看。
更過關於redis操作和學習教程請進入碼神營地官網:www.icodegod.com
-
數據結構
首先看看哈希表節點實體的結構,簡單的由key,v,和一個指向下一個節點的指針next。其中key是void*,這樣定義可以支持任意數據類型的key,v是一個共用體,支持void*,uint64_t,int64_t,和double,這麼做得目的還是爲了區分類型,節省空間。內存結構圖如下:
v的內存結構:共用8字節內存空間
第二個結構體是字典類型特定函數結構體,裏面是字典類型特定函數,包括hashFunction哈希函數,keyDup鍵複製函數,valDup值複製函數,keyCompare鍵複製函數,keyDestructor鍵銷燬函數,valDestructor值銷燬函數。dictType結構體類型存在的意義就是爲實現特定類型字典提供便利。
源碼中哈希表的結構定義和字典的定義:
dictht是哈希表結構,4部分組成,table是一個指向dicEntry的指針數組,其中每一個元素都是一個dicEntry的結構體,size表示hash表的長度,sizemask大小掩碼用於rehash中,used表示該hash表中已使用長度。
dict是redis中字典的結構,5部分組成,type是一個dictType結構的指針,privdata是void*的指針,用來保存dictType裏面函數傳遞的私有數據,ht[2]是一個大小爲2的dictht類型的數組,每一個元素都是一個dictht類型的哈希表,其中ht[0]用於字典使用,ht[1]用於rehash使用,用來對ht[0]進行擴容或者縮容處理,作爲後備哈希表,rehashindx標誌rehash進度,當沒有進行rehash的時候,該值爲-1,表示沒有進行過一次在哈希操作,以及字典的迭代器iterators。
源碼中iterators的結構體:
d指針指向當前所要操作的字典,index表示下標,table是哈希表,safe用於標誌新表格還是舊錶,entry表示當前字典實體,nextEntry表示下一個字典實體。fingerprint表示指紋標記,作用是避免不安全的迭代器迭代現象。 -
內存結構
看完整個字典涉及到的數據結構可以得到整個字典的內存結構圖(圖太難畫,盜用redis設計與實現書中圖):
-
字典創建
在源碼中字典創建使用函數dictCreate創建,具體實現如下:
創建字典的過程分爲兩部分:空間開闢,變量初始化。- 空間開闢。redis源碼中的所以內存開闢都採用封裝的zmalloc進行分配,zmalloc底層封裝了c語言函數malloc來進行實現。
- 變量初始化。該過程初始化兩個哈希表數據,指針指向null,hash表格大小爲0,字典開始不參與rehash,賦予rehashidx = -1。
-
添加元素
創建完一個字典,要想實現往該字典中添加元素實現步驟如下:
1. 如果該字典中已經有元素且hash表空間足夠能容下這一個鍵值對,那麼不需要進行hash空間再分配操作:
/* Add an element to the target hash table */
int dictAdd(dict *d, void *key, void *val)
{
dictEntry *entry = dictAddRaw(d,key,NULL);
if (!entry) return DICT_ERR;
dictSetVal(d, entry, val);
return DICT_OK;
}
從源碼可知,先做了設置key的操作,然後再做設置val操作。
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
int index;
dictEntry *entry;
dictht *ht;
if (dictIsRehashing(d)) _dictRehashStep(d);
/* Get the index of the new element, or -1 if
* the element already exists. */
if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
return NULL;
/* Allocate the memory and store the new entry.
* Insert the element in top, with the assumption that in a database
* system it is more likely that recently added entries are accessed
* more frequently. */
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
/* Set the hash entry fields. */
dictSetKey(d, entry, key);
return entry;
}
設置key操作中首先會檢查該字典中是否使用了rehash,判斷rehash進行到哪一步了,然後會判斷該key是否已經存在,如果存在則直接返回null,無需再重新創建。如果該key值已經存在則返回null,如果該key不存在則判斷使用哪個哈希表進行新的實體採用頭插法插入,然後設置新的實體的key字段。
設置完key調用設置值函數dictSetVal設置key對應值。因爲設置key對應的值比較簡單,redis對應源碼中將該方法設置爲宏定義:
#define dictSetVal(d, entry, _val_) do { \
if ((d)->type->valDup) \
(entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \
else \
(entry)->v.val = (_val_); \
} while(0)