久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Redis中內部數據結構dict的作用是什么

166次閱讀
沒有評論

共計 8365 個字符,預計需要花費 21 分鐘才能閱讀完成。

本篇文章為大家展示了 Redis 中內部數據結構 dict 的作用是什么,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

dict 的數據結構定義

為了實現增量式重哈希(incremental rehashing),dict 的數據結構里包含兩個哈希表。在重哈希期間,數據從第一個哈希表向第二個哈希表遷移。

dict 的 C 代碼定義如下(出自 Redis 源碼 dict.h):

typedef struct dictEntry {
 void *key;
 union {
 void *val;
 uint64_t u64;
 int64_t s64;
 double d;
 } v;
 struct dictEntry *next;
} dictEntry;
typedef struct dictType { unsigned int (*hashFunction)(const void *key);
 void *(*keyDup)(void *privdata, const void *key);
 void *(*valDup)(void *privdata, const void *obj);
 int (*keyCompare)(void *privdata, const void *key1, const void *key2);
 void (*keyDestructor)(void *privdata, void *key);
 void (*valDestructor)(void *privdata, void *obj);
} dictType;
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
 dictEntry **table;
 unsigned long size;
 unsigned long sizemask;
 unsigned long used;
} dictht;
typedef struct dict {
 dictType *type;
 void *privdata;
 dictht ht[2];
 long rehashidx; /* rehashing not in progress if rehashidx == -1 */
 int iterators; /* number of iterators currently running */
} dict;

為了能更清楚地展示 dict 的數據結構定義,我們用一張結構圖來表示它。如下。

結合上面的代碼和結構圖,可以很清楚地看出 dict 的結構。一個 dict 由如下若干項組成:

一個指向 dictType 結構的指針(type)。它通過自定義的方式使得 dict 的 key 和 value 能夠存儲任何類型的數據。

一個私有數據指針(privdata)。由調用者在創建 dict 的時候傳進來。

兩個哈希表(ht[2])。只有在重哈希的過程中,ht[0]和 ht[1]才都有效。而在平常情況下,只有 ht[0]有效,ht[1]里面沒有任何數據。上圖表示的就是重哈希進行到中間某一步時的情況。

當前重哈希索引(rehashidx)。如果 rehashidx = -1,表示當前沒有在重哈希過程中;否則,表示當前正在進行重哈希,且它的值記錄了當前重哈希進行到哪一步了。

當前正在進行遍歷的 iterator 的個數。這不是我們現在討論的重點,暫時忽略。

dictType 結構包含若干函數指針,用于 dict 的調用者對涉及 key 和 value 的各種操作進行自定義。這些操作包含:

hashFunction,對 key 進行哈希值計算的哈希算法。

keyDup 和 valDup,分別定義 key 和 value 的拷貝函數,用于在需要的時候對 key 和 value 進行深拷貝,而不僅僅是傳遞對象指針。

keyCompare,定義兩個 key 的比較操作,在根據 key 進行查找時會用到。

keyDestructor 和 valDestructor,分別定義對 key 和 value 的析構函數。

私有數據指針(privdata)就是在 dictType 的某些操作被調用時會傳回給調用者。

需要詳細察看的是 dictht 結構。它定義一個哈希表的結構,由如下若干項組成:

一個 dictEntry 指針數組(table)。key 的哈希值最終映射到這個數組的某個位置上(對應一個 bucket)。如果多個 key 映射到同一個位置,就發生了沖突,那么就拉出一個 dictEntry 鏈表。

size:標識 dictEntry 指針數組的長度。它總是 2 的指數。

sizemask:用于將哈希值映射到 table 的位置索引。它的值等于 (size-1),比如 7, 15, 31, 63,等等,也就是用二進制表示的各個 bit 全 1 的數字。每個 key 先經過 hashFunction 計算得到一個哈希值,然后計算(哈希值 sizemask) 得到在 table 上的位置。相當于計算取余(哈希值 % size)。

used:記錄 dict 中現有的數據個數。它與 size 的比值就是裝載因子(load factor)。這個比值越大,哈希值沖突概率越高。

dictEntry 結構中包含 k, v 和指向鏈表下一項的 next 指針。k 是 void 指針,這意味著它可以指向任何類型。v 是個 union,當它的值是 uint64_t、int64_t 或 double 類型時,就不再需要額外的存儲,這有利于減少內存碎片。當然,v 也可以是 void 指針,以便能存儲任何類型的數據。

dict 的創建(dictCreate)

dict *dictCreate(dictType *type,
 void *privDataPtr)
 dict *d = zmalloc(sizeof(*d));
 _dictInit(d,type,privDataPtr);
 return d;
int _dictInit(dict *d, dictType *type,
 void *privDataPtr)
 _dictReset(d- ht[0]);
 _dictReset(d- ht[1]);
 d- type = type;
 d- privdata = privDataPtr;
 d- rehashidx = -1;
 d- iterators = 0;
 return DICT_OK;
static void _dictReset(dictht *ht)
 ht- table = NULL;
 ht- size = 0;
 ht- sizemask = 0;
 ht- used = 0;
}

dictCreate 為 dict 的數據結構分配空間并為各個變量賦初值。其中兩個哈希表 ht[0]和 ht[1]起始都沒有分配空間,table 指針都賦為 NULL。這意味著要等第一個數據插入時才會真正分配空間。

dict 的查找(dictFind)

#define dictIsRehashing(d) ((d)- rehashidx != -1)
dictEntry *dictFind(dict *d, const void *key)
 dictEntry *he;
 unsigned int h, idx, table;
 if (d- ht[0].used + d- ht[1].used == 0) return NULL; /* dict is empty */
 if (dictIsRehashing(d)) _dictRehashStep(d);
 h = dictHashKey(d, key);
 for (table = 0; table  = 1; table++) { idx = h   d- ht[table].sizemask;
 he = d- ht[table].table[idx];
 while(he) { if (key==he- key || dictCompareKeys(d, key, he- key))
 return he;
 he = he- next;
 }
 if (!dictIsRehashing(d)) return NULL;
 }
 return NULL;
}

上述 dictFind 的源碼,根據 dict 當前是否正在重哈希,依次做了這么幾件事:

如果當前正在進行重哈希,那么將重哈希過程向前推進一步(即調用_dictRehashStep)。實際上,除了查找,插入和刪除也都會觸發這一動作。這就將重哈希過程分散到各個查找、插入和刪除操作中去了,而不是集中在某一個操作中一次性做完。

計算 key 的哈希值(調用 dictHashKey,里面的實現會調用前面提到的 hashFunction)。

先在第一個哈希表 ht[0]上進行查找。在 table 數組上定位到哈希值對應的位置(如前所述,通過哈希值與 sizemask 進行按位與),然后在對應的 dictEntry 鏈表上進行查找。查找的時候需要對 key 進行比較,這時候調用 dictCompareKeys,它里面的實現會調用到前面提到的 keyCompare。如果找到就返回該項。否則,進行下一步。

判斷當前是否在重哈希,如果沒有,那么在 ht[0]上的查找結果就是最終結果(沒找到,返回 NULL)。否則,在 ht[1]上進行查找(過程與上一步相同)。

下面我們有必要看一下增量式重哈希的_dictRehashStep 的實現。

static void _dictRehashStep(dict *d) { if (d- iterators == 0) dictRehash(d,1);
int dictRehash(dict *d, int n) {
 int empty_visits = n*10; /* Max number of empty buckets to visit. */
 if (!dictIsRehashing(d)) return 0;
 while(n--   d- ht[0].used != 0) {
 dictEntry *de, *nextde;
 /* Note that rehashidx can t overflow as we are sure there are more
 * elements because ht[0].used != 0 */
 assert(d- ht[0].size   (unsigned long)d- rehashidx);
 while(d- ht[0].table[d- rehashidx] == NULL) {
 d- rehashidx++;
 if (--empty_visits == 0) return 1;
 }
 de = d- ht[0].table[d- rehashidx];
 /* Move all the keys in this bucket from the old to the new hash HT */
 while(de) {
 unsigned int h;
 nextde = de- next;
 /* Get the index in the new hash table */
 h = dictHashKey(d, de- key)   d- ht[1].sizemask;
 de- next = d- ht[1].table[h];
 d- ht[1].table[h] = de;
 d- ht[0].used--;
 d- ht[1].used++;
 de = nextde;
 }
 d- ht[0].table[d- rehashidx] = NULL;
 d- rehashidx++;
 }
 /* Check if we already rehashed the whole table... */
 if (d- ht[0].used == 0) { zfree(d- ht[0].table);
 d- ht[0] = d- ht[1];
 _dictReset(d- ht[1]);
 d- rehashidx = -1;
 return 0;
 }
 /* More to rehash... */
 return 1;
}

dictRehash 每次將重哈希至少向前推進 n 步(除非不到 n 步整個重哈希就結束了),每一步都將 ht[0]上某一個 bucket(即一個 dictEntry 鏈表)上的每一個 dictEntry 移動到 ht[1]上,它在 ht[1]上的新位置根據 ht[1]的 sizemask 進行重新計算。rehashidx 記錄了當前尚未遷移(有待遷移)的 ht[0]的 bucket 位置。

如果 dictRehash 被調用的時候,rehashidx 指向的 bucket 里一個 dictEntry 也沒有,那么它就沒有可遷移的數據。這時它嘗試在 ht[0].table 數組中不斷向后遍歷,直到找到下一個存有數據的 bucket 位置。如果一直找不到,則最多走 n *10 步,本次重哈希暫告結束。

最后,如果 ht[0]上的數據都遷移到 ht[1]上了(即 d - ht[0].used == 0),那么整個重哈希結束,ht[0]變成 ht[1]的內容,而 ht[1]重置為空。

根據以上對于重哈希過程的分析,我們容易看出,本文前面的 dict 結構圖中所展示的正是 rehashidx= 2 時的情況,前面兩個 bucket(ht[0].table[0]和 ht[0].table[1])都已經遷移到 ht[1]上去了。

dict 的插入(dictAdd 和 dictReplace)

dictAdd 插入新的一對 key 和 value,如果 key 已經存在,則插入失敗。

dictReplace 也是插入一對 key 和 value,不過在 key 存在的時候,它會更新 value。

int dictAdd(dict *d, void *key, void *val)
 dictEntry *entry = dictAddRaw(d,key);
 if (!entry) return DICT_ERR;
 dictSetVal(d, entry, val);
 return DICT_OK;
dictEntry *dictAddRaw(dict *d, void *key)
 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)) == -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;
static int _dictKeyIndex(dict *d, const void *key)
 unsigned int h, idx, table;
 dictEntry *he;
 /* Expand the hash table if needed */
 if (_dictExpandIfNeeded(d) == DICT_ERR)
 return -1;
 /* Compute the key hash value */
 h = dictHashKey(d, key);
 for (table = 0; table  = 1; table++) { idx = h   d- ht[table].sizemask;
 /* Search if this slot does not already contain the given key */
 he = d- ht[table].table[idx];
 while(he) { if (key==he- key || dictCompareKeys(d, key, he- key))
 return -1;
 he = he- next;
 }
 if (!dictIsRehashing(d)) break;
 }
 return idx;
}

以上是 dictAdd 的關鍵實現代碼。我們主要需要注意以下幾點:

它也會觸發推進一步重哈希(_dictRehashStep)。

如果正在重哈希中,它會把數據插入到 ht[1];否則插入到 ht[0]。

在對應的 bucket 中插入數據的時候,總是插入到 dictEntry 的頭部。因為新數據接下來被訪問的概率可能比較高,這樣再次查找它時就比較次數較少。

_dictKeyIndex 在 dict 中尋找插入位置。如果不在重哈希過程中,它只查找 ht[0];否則查找 ht[0]和 ht[1]。

_dictKeyIndex 可能觸發 dict 內存擴展(_dictExpandIfNeeded,它將哈希表長度擴展為原來兩倍,具體請參考 dict.c 中源碼)。

dictReplace 在 dictAdd 基礎上實現,如下:

int dictReplace(dict *d, void *key, void *val)
 dictEntry *entry, auxentry;
 /* Try to add the element. If the key
 * does not exists dictAdd will suceed. */
 if (dictAdd(d, key, val) == DICT_OK)
 return 1;
 /* It already exists, get the entry */
 entry = dictFind(d, key);
 /* Set the new value and free the old one. Note that it is important
 * to do that in this order, as the value may just be exactly the same
 * as the previous one. In this context, think to reference counting,
 * you want to increment (set), and then decrement (free), and not the
 * reverse. */
 auxentry = *entry;
 dictSetVal(d, entry, val);
 dictFreeVal(d,  auxentry);
 return 0;
}

在 key 已經存在的情況下,dictReplace 會同時調用 dictAdd 和 dictFind,這其實相當于兩次查找過程。這里 Redis 的代碼不夠優化。

dict 的刪除(dictDelete)

dictDelete 的源碼這里忽略,具體請參考 dict.c。需要稍加注意的是:

dictDelete 也會觸發推進一步重哈希(_dictRehashStep)

如果當前不在重哈希過程中,它只在 ht[0]中查找要刪除的 key;否則 ht[0]和 ht[1]它都要查找。

刪除成功后會調用 key 和 value 的析構函數(keyDestructor 和 valDestructor)。

上述內容就是 Redis 中內部數據結構 dict 的作用是什么,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注丸趣 TV 行業資訊頻道。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-03發表,共計8365字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 香格里拉县| 高碑店市| 彰化县| 镶黄旗| 乐都县| 拜泉县| 和顺县| 沙河市| 文化| 长寿区| 登封市| 丰城市| 万山特区| 桃江县| 霍山县| 武威市| 和林格尔县| 邵武市| 沁水县| 永康市| 宜丰县| 广宁县| 德昌县| 南川市| 海南省| 北碚区| 阳江市| 济源市| 定南县| 即墨市| 徐汇区| 桐城市| 文化| 中江县| 壤塘县| 合肥市| 普洱| 兰考县| 达日县| 临沂市| 澄城县|