redis源碼分析(4)-- 基本數據結構字典dict

1、字典結構redis

Redis中字典採用hash表結構,以下:數組

typedef struct dictht {
    dictEntry **table; // hash表數組
    unsigned long size; // hash表大小
    unsigned long sizemask; // 掩碼
    unsigned long used; // 已經使用的大小
} dictht;

table是一個數組,每一個元素指向一個dictEntry結構。size表示hash表大小,used表示使用的大小。一個size=4的空hash表以下:函數

dictEntry是一個key-value pair, 定義爲:ui

 1 typedef struct dictEntry {
 2     void *key; // key
 3     union { 
 4         void *val;
 5         uint64_t u64;
 6         int64_t s64;
 7         double d;
 8     } v; // value
 9     struct dictEntry *next; // 指向下一個key-value
10 } dictEntry;

next指針用於解決hash衝突,redis總採用直接鏈址法解決衝突。舉例:this

Redis中字典定義:spa

typedef struct dict {
    dictType *type; // type和privdata區別操做不一樣類型key-value
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

ht[2]中通常只有ht[0]使用,ht[1]在rehash時使用,ht[1]和rehashindex使用後續介紹。指針

 2、hash實現code

Redis中使用的hash函數爲MurmurHash2, 定義爲:blog

1 unsigned int dictGenHashFunction(const void *key, int len)

經過宏定義:element

1 #define dictHashKey(d, key) (d)->type->hashFunction(key)

獲取hashkey的值,以後使用:

1 idx = hashkey & d->ht[table].sizemask;

獲得hash桶的座標, 如圖:

Redis中使用直接鏈址法解決衝突,如圖:

 3、Rehash

在函數_dictExpandIfNeeded中會判斷是否須要擴展hash表:

static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}

能夠看出,當利用率used/size到某個比例時,開始執行hash表擴展,進行rehash。流程爲:

 1 int dictRehash(dict *d, int n) {
 2     int empty_visits = n*10; /* Max number of empty buckets to visit. */
 3     if (!dictIsRehashing(d)) return 0;
 4 
 5     while(n-- && d->ht[0].used != 0) {
 6         dictEntry *de, *nextde;
 7 
 8         /* Note that rehashidx can't overflow as we are sure there are more
 9          * elements because ht[0].used != 0 */
10         assert(d->ht[0].size > (unsigned long)d->rehashidx);
11         while(d->ht[0].table[d->rehashidx] == NULL) {
12             d->rehashidx++;
13             if (--empty_visits == 0) return 1;
14         }
15         de = d->ht[0].table[d->rehashidx];
16         /* Move all the keys in this bucket from the old to the new hash HT */
17         while(de) {
18             unsigned int h;
19 
20             nextde = de->next;
21             /* Get the index in the new hash table */
22             h = dictHashKey(d, de->key) & d->ht[1].sizemask;
23             de->next = d->ht[1].table[h];
24             d->ht[1].table[h] = de;
25             d->ht[0].used--;
26             d->ht[1].used++;
27             de = nextde;
28         }
29         d->ht[0].table[d->rehashidx] = NULL;
30         d->rehashidx++;
31     }
32 
33     /* Check if we already rehashed the whole table... */
34     if (d->ht[0].used == 0) {// 遷移完畢,更新ht[0]
35         zfree(d->ht[0].table);
36         d->ht[0] = d->ht[1];
37         _dictReset(&d->ht[1]);
38         d->rehashidx = -1;
39         return 0;
40     }
41 
42     /* More to rehash... */
43     return 1;
44 }

把ht[0]上的數據逐步遷移到ht[1].

4、字典主要API

相關文章
相關標籤/搜索