redis4.0.11字典

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct dictType {
    uint64_t (*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 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

字典dict主要內容在於2個dictht表和rehashidx,dictht[0]表示沒有進行哈希的表,dict[1]表示正在進行哈希的表,rehashidx不爲-1的時候表示正在進行哈希。dictht主要內容sizemask始終是size減1。

int dictExpand(dict *d, unsigned long size)

用來擴大字典大小,字典大小永遠爲2的倍數,方便查找。如果0號表沒有初始化過,那麼直接給0號表賦值,如果0號表存在的話,就擴大的表賦值給1號表,準備哈希。

int dictRehash(dict *d, int n)

對字典進行哈希,參數n表示要執行次數。由於字典會有空值,所以設置空值瀏覽次數,這個值設置爲執行次數的10倍。每次執行哈希的過程如下:首先從0號表中找到一個非空實體,把這個實體鏈表裏的內容全部遷移到1號表對應下標的表裏,往鏈表首部賦值。如果哈希完成則把1號表的內容全部遷移回0號表,清空1號表並返回0,否則返回1。

下圖引用自https://redissrc.readthedocs.io/en/latest/datastruct/dict.html,把字典中添加新元素的過程描述的很清楚了。

/* Function to reverse bits. Algorithm from:
 * http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
static unsigned long rev(unsigned long v) {
    unsigned long s = 8 * sizeof(v); // bit size; must be power of 2
    unsigned long mask = ~0;
    while ((s >>= 1) > 0) {
        mask ^= (mask << s);
        v = ((v >> s) & mask) | ((v << s) & ~mask);
    }
    return v;
}

這個對一個數進行高低位相反的操作的算法蠻有意思的,網站上講解得很詳細。

unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction* bucketfn, void *privdata)

字典的數據結構和算法裏,dictScan這個函數的算法特別精妙。遍歷的步驟如下:第一步調用函數,遊標(v)設置爲0;第二步執行一次遍歷,會返回一個新的遊標,供下一次調用使用;第三步當遊標返回爲0的時候整個遍歷就結束了。

由於字典在遍歷的時候大小可能會擴大或者縮小,所以普通的遍歷算法難免會過多或過少地遍歷字典,雖然這個算法也會多遍歷幾次字典,但是它保證了一定會遍歷所有元素。

字典的大小一定是2的n次方,字典的掩碼是n位全1的數,所以取元素的時候是根據哈希值並掩碼取得,保證遊標一定在字典分配的大小範圍內。遍歷的時候字典的大小沒有發生變化的這種情況沒有什麼好說的,關鍵在於字典的大小發生變化的時候。

假設字典的大小是16,掩碼就是0b1111,以字典遍歷到0b1100字典大小發生變化爲例。

當字典的大小變大到32時,當前遊標就變成了0b01100。字典遍歷過的遊標由0b0000,0b1000,0b0100變爲0b00000,0b10000,0b01000,0b11000,0b00100,0b10100,也就是說之前遍歷過的遊標不會再遍歷到。

當字典的大小變小到8時,當前遊標就變成了0b100。字典遍歷過的遊標由0b0000,0b1000,0b0100變爲0b000,0b100,這時候會重新遍歷一次0b100這個遊標,但是也保證了所有元素會被遍歷到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章