主頁 > 資料庫 > redis 5.0.7 原始碼閱讀——字典dict

redis 5.0.7 原始碼閱讀——字典dict

2020-09-13 09:14:04 資料庫

redis中字典相關的檔案為:dict.h與dict.c

與其說是一個字典,道不如說是一個哈希表,

一、資料結構

dictEntry

 1 typedef struct dictEntry {
 2     void *key;
 3     union {
 4         void *val;
 5         uint64_t u64;
 6         int64_t s64;
 7         double d;
 8     } v;
 9     struct dictEntry *next;
10 } dictEntry;

dictEntry是一個kv對的單向鏈表,其中v是一個聯合體,支持數字,或者是指向一塊記憶體的指標,

 1 /*
 2 +---------------+
 3 |void *key      |
 4 +---------------+
 5 |union{...} v   |
 6 +---------------+
 7 |dictEntry *next|---+
 8 +---------------+   |
 9                     |
10 +---------------+ <-+
11 |void *key      |
12 +---------------+
13 |union{...} v   |
14 +---------------+
15 |dictEntry *next|
16 +---------------+
17 */

為了節約篇幅,后續用以下結構表示:

1 /*
2 +---+  +---+
3 |K|V|->|K|V|->NULL
4 +---+  +---+
5 */

 

dictht

 1 typedef struct dictht {
 2     dictEntry **table;
 3     unsigned long size;
 4     /*
 5         這樣寫可能更容易理解
 6         const unsigned long size = 4;
 7         dictEntry *table[size];
 8     */
 9     
10 
11     unsigned long sizemask;
12     //sizemask,始終為size-1
13 
14     unsigned long used;
15     //當前總dictEntry數量
16 } dictht;

dictht是一個hash table,整體結構大致為:

 1 /*
 2 +----------------------+   +---> +-----------------+  +---+
 3 |dictEntry **table     |---+     |dictEntry *bucket|->|K|V|->NULL
 4 +----------------------+         +-----------------+  +---+
 5 |unsigned long size = 4|         |dictEntry *bucket|->NULL
 6 +----------------------+         +-----------------+
 7 |unsigned long sizemask|         |dictEntry *bucket|->NULL
 8 +----------------------+         +-----------------+
 9 |unsigned long used    |         |dictEntry *bucket|->NULL
10 +----------------------+         +-----------------+ 
11 */   

其中,table指向大小為sizeof(dictEntry*) * size的一片記憶體空間,每個dictEntry*可以視為一個bucket,每個bucket下掛著一個dictEntry單向鏈表,

size的值始終為2的位數,而sizemask的值始終為size-1,其作用是決定kv對要掛在哪個bucket上,

舉個例子,size=4時,sizemask=3,其二進制為 0011,若通過hash函式計算出來key對應的hash值hash_value為5,二進制為0101,則通過位運算 sizemask & hash_value = https://www.cnblogs.com/chinxi/p/0011 & 0101 = 0001,十進制為1,則將會掛在idx = 1的bucket上,

 

dictType

1 typedef struct dictType {
2     uint64_t (*hashFunction)(const void *key);
3     void *(*keyDup)(void *privdata, const void *key);
4     void *(*valDup)(void *privdata, const void *obj);
5     int (*keyCompare)(void *privdata, const void *key1, const void *key2);
6     void (*keyDestructor)(void *privdata, void *key);
7     void (*valDestructor)(void *privdata, void *obj);
8 } dictType;

dictType用于自定義一些操作的方法,如拷貝key、拷貝value、銷毀key、銷毀value,比較key,以及hash函式,

 

dict

1 typedef struct dict {
2     dictType *type;
3     void *privdata;
4     dictht ht[2];
5     long rehashidx; /* rehashing not in progress if rehashidx == -1 */
6     unsigned long iterators; /* number of iterators currently running */
7 } dict;

之前提到的dictType與dictht都是dict的成員變數,除此之外,還有privdata,是在創建dict的時候呼叫者傳入,用于特定操作時回傳給函式的,如:

 1 #define dictFreeVal(d, entry) \
 2     if ((d)->type->valDestructor) \
 3         (d)->type->valDestructor((d)->privdata, (entry)->v.val)
 4 
 5 #define dictSetVal(d, entry, _val_) do { \
 6     if ((d)->type->valDup) \
 7         (entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \
 8     else \
 9         (entry)->v.val = (_val_); \
10 } while(0)
11 
12 #define dictSetSignedIntegerVal(entry, _val_) \
13     do { (entry)->v.s64 = _val_; } while(0)
14 
15 #define dictSetUnsignedIntegerVal(entry, _val_) \
16     do { (entry)->v.u64 = _val_; } while(0)
17 
18 #define dictSetDoubleVal(entry, _val_) \
19     do { (entry)->v.d = _val_; } while(0)
20 
21 #define dictFreeKey(d, entry) \
22     if ((d)->type->keyDestructor) \
23         (d)->type->keyDestructor((d)->privdata, (entry)->key)
24 
25 #define dictSetKey(d, entry, _key_) do { \
26     if ((d)->type->keyDup) \
27         (entry)->key = (d)->type->keyDup((d)->privdata, _key_); \
28     else \
29         (entry)->key = (_key_); \
30 } while(0)
31 
32 #define dictCompareKeys(d, key1, key2) \
33     (((d)->type->keyCompare) ? \
34         (d)->type->keyCompare((d)->privdata, key1, key2) : \
35         (key1) == (key2))

rehashidx,是與ht[2]配合實作漸進式rehash操作的,若使用一步到位的方式,當key的數量非常大的時候,rehashing期間,是會卡死所有操作的,

iterators,是用于記錄當前使用的安全迭代器數量,與rehashing操作有關, 整體結構如下:
 1 /*
 2 +---------+    /+-----------+   +-->+----------+  +---+
 3 |dictType*|   / |dictEntry**|---+   |dictEntry*|->|K|V|->NULL
 4 +---------+  /  +-----------+       +----------+  +---+
 5 |privdata | /   |size       |       |dictEntry*|->NULL
 6 +---------+/    +-----------+       +----------+
 7 |ht[2]    |     |sizemask   |       |dictEntry*|->NULL
 8 +---------+\    +-----------+       +----------+
 9 |rehashidx| \   |used       |       |dictEntry*|->NULL
10 +---------+  \  +-----------+       +----------+ 
11 |iterators|   \ 
12 +---------+    \+-----------+
13                 |dictEntry**|-->NULL
14                 +-----------+
15                 |size       |
16                 +-----------+
17                 |sizemask   |
18                 +-----------+
19                 |used       |
20                 +-----------+
21 */   

 

二、創建

 1 static void _dictReset(dictht *ht)
 2 {
 3     ht->table = NULL;
 4     ht->size = 0;
 5     ht->sizemask = 0;
 6     ht->used = 0;
 7 }
 8 
 9 int _dictInit(dict *d, dictType *type,
10         void *privDataPtr)
11 {
12     _dictReset(&d->ht[0]);
13     _dictReset(&d->ht[1]);
14     d->type = type;
15     d->privdata =https://www.cnblogs.com/chinxi/p/ privDataPtr;
16     d->rehashidx = -1;
17     d->iterators = 0;
18     return DICT_OK;
19 }
20 
21 dict *dictCreate(dictType *type,
22         void *privDataPtr)
23 {
24     dict *d = zmalloc(sizeof(*d));
25 
26     _dictInit(d,type,privDataPtr);
27     return d;
28 }

可以呼叫dictCreate創建一個空的dict,它會分配好dict的空間,并初始化所有成員變數,在這里把privdata傳入并保存,搜了一下整個redis原始碼的dictCreate呼叫,看到傳入的值全為NULL,目前的理解暫時不清楚這個變數是什么時候賦值的,初始化后的dict結構如下:

 1 /*
 2 +------------+    /+-----------+   
 3 |dictType*   |   / |dictEntry**|-->NULL
 4 +------------+  /  +-----------+   
 5 |privdata    | /   |size=0     |   
 6 +------------+/    +-----------+   
 7 |ht[2]       |     |sizemask=0 |   
 8 +------------+\    +-----------+   
 9 |rehashidx=-1| \   |used=0     |   
10 +------------+  \  +-----------+   
11 |iterators=0 |   \ 
12 +------------+    \+-----------+
13                    |dictEntry**|-->NULL
14                    +-----------+
15                    |size=0     |
16                    +-----------+
17                    |sizemask=0 |
18                    +-----------+
19                    |used=0     |
20                    +-----------+
21 */ 

剛創建好的dict是存不了任何資料的,其兩個hash table的size都為0,這里先說明一下resize操作:

 1 #define DICT_HT_INITIAL_SIZE     4
 2 
 3 static unsigned long _dictNextPower(unsigned long size)
 4 {
 5     unsigned long i = DICT_HT_INITIAL_SIZE;
 6 
 7     if (size >= LONG_MAX) return LONG_MAX + 1LU;
 8     while(1) {
 9         if (i >= size)
10             return i;
11         i *= 2;
12     }
13 }
14 
15 /* Expand or create the hash table */
16 int dictExpand(dict *d, unsigned long size)
17 {
18     /* the size is invalid if it is smaller than the number of
19      * elements already inside the hash table */
20     if (dictIsRehashing(d) || d->ht[0].used > size)
21         return DICT_ERR;
22 
23     dictht n; /* the new hash table */
24     unsigned long realsize = _dictNextPower(size);
25 
26     /* Rehashing to the same table size is not useful. */
27     if (realsize == d->ht[0].size) return DICT_ERR;
28 
29     /* Allocate the new hash table and initialize all pointers to NULL */
30     n.size = realsize;
31     n.sizemask = realsize-1;
32     n.table = zcalloc(realsize*sizeof(dictEntry*));
33     n.used = 0;
34 
35     /* Is this the first initialization? If so it's not really a rehashing
36      * we just set the first hash table so that it can accept keys. */
37     if (d->ht[0].table == NULL) {
38         d->ht[0] = n;
39         return DICT_OK;
40     }
41 
42     /* Prepare a second hash table for incremental rehashing */
43     d->ht[1] = n;
44     d->rehashidx = 0;
45     return DICT_OK;
46 }
47 
48 int dictResize(dict *d)
49 {
50     int minimal;
51 
52     if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
53     minimal = d->ht[0].used;
54     if (minimal < DICT_HT_INITIAL_SIZE)
55         minimal = DICT_HT_INITIAL_SIZE;
56     return dictExpand(d, minimal);
57 }

_dictNextPower用于獲取當前要分配給hash table的size,得到的值一定是2的倍數,初始值為4,

dictExpand,從原始碼注釋上看,它是為了擴容hash table,或者創建一個,它不允許與rehashing操作同時進行,也不能強制縮容,在使用_dictNextPower得到需要的size之后,它先是使用一個臨時變數n去分配空間,然后進行判斷,若ht[0].table的值為NULL,則認為是剛create出來的dict,直接把n賦值給ht[0],否則給ht[1],并開始rehashing操作,

dictResize操作就不用多說了,

 

三、rehashing操作

若有這樣一個dict,假設K1、K2、K3、K4計算出來的hash值分別為0、5、2、7,使用sizemask計算出來的idx分別為0、1、2、3

 1 /*
 2                                                       +----+
 3                                                    +->|K1|V|->NULL
 4 +------------+    /+-----------+  +->+----------+ /   +----+
 5 |dictType*   |   / |dictEntry**|--+  |dictEntry*|/    +----+
 6 +------------+  /  +-----------+     +----------+ +-->|K2|V|->NULL
 7 |privdata    | /   |size=4     |     |dictEntry*|/    +----+
 8 +------------+/    +-----------+     +----------+
 9 |ht[2]       |     |sizemask=3 |     |dictEntry*|\    +----+
10 +------------+\    +-----------+     +----------+ +-->|K3|V|->NULL
11 |rehashidx=-1| \   |used=4     |     |dictEntry*|\    +----+
12 +------------+  \  +-----------+     +----------+ \   +----+
13 |iterators=0 |   \                                 +->|K4|V|->NULL
14 +------------+    \+-----------+                      +----+
15                    |dictEntry**|-->NULL
16                    +-----------+
17                    |size=0     |
18                    +-----------+
19                    |sizemask=0 |
20                    +-----------+
21                    |used=0     |
22                    +-----------+
23 */ 
 1 static int _dictExpandIfNeeded(dict *d)
 2 {
 3     /* Incremental rehashing already in progress. Return. */
 4     if (dictIsRehashing(d)) return DICT_OK;
 5 
 6     /* If the hash table is empty expand it to the initial size. */
 7     if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
 8 
 9     /* If we reached the 1:1 ratio, and we are allowed to resize the hash
10      * table (global setting) or we should avoid it but the ratio between
11      * elements/buckets is over the "safe" threshold, we resize doubling
12      * the number of buckets. */
13     if (d->ht[0].used >= d->ht[0].size &&
14         (dict_can_resize ||
15          d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
16     {
17         return dictExpand(d, d->ht[0].used*2);
18     }
19     return DICT_OK;
20 }

通過函式_dictExpandIfNeeded,可知當used >= size且dict_can_resize == TRUE的時候,需要呼叫dictExpand進入rehashing狀態,dict_can_resize默認為1

1 static int dict_can_resize = 1;
2 static unsigned int dict_force_resize_ratio = 5;

需要的size為當前used * 2,即為8,呼叫dictExpand之后的結構:

 1 /*
 2                                                        +----+
 3                                                     +->|K1|V|->NULL
 4                                    +->+----------+ /   +----+
 5                                    |  |dictEntry*|/    +----+
 6                                    |  +----------+ +-->|K2|V|->NULL
 7                                    |  |dictEntry*|/    +----+
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
10  +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
11  |privdata    | /   |size=4     |     |dictEntry*|\    +----+
12  +------------+/    +-----------+     +----------+ \   +----+
13  |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
14  +------------+\    +-----------+                      +----+
15  |rehashidx=0 | \   |used=4     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+
19                     |dictEntry**|--+  |dictEntry*|->NULL
20                     +-----------+     +----------+
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+
23                     |sizemask=7 |     |dictEntry*|->NULL
24                     +-----------+     +----------+
25                     |used=0     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+
29                                       |dictEntry*|->NULL
30                                       +----------+
31                                       |dictEntry*|->NULL
32                                       +----------+
33                                       |dictEntry*|->NULL
34                                       +----------+
35 */

根據rehashing操作

 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             uint64_t 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) {
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 }

rehashing操作將會把ht[0]里,rehashidx的值對應的bucket下的所有dictEntry,移至ht[1],之后對rehashidx進行自增處理,當ht[0]->used為0時,認為ht[0]的所有dictEntry已經移至ht[1],此時return 0,否則 return 1,告訴呼叫者,還需要繼續進行rehashing操作,同時,rehashing時允許最多跳過10n的空bucket,就要退出流程,假設傳入的n=1,即只進行一次rehashing操作,轉換至完成之后的結構:

 1 /*
 2                                                        
 3                                                     +->NULL
 4                                    +->+----------+ /   
 5                                    |  |dictEntry*|/    +----+
 6                                    |  +----------+ +-->|K2|V|->NULL
 7                                    |  |dictEntry*|/    +----+
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
10  +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
11  |privdata    | /   |size=4     |     |dictEntry*|\    +----+
12  +------------+/    +-----------+     +----------+ \   +----+
13  |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
14  +------------+\    +-----------+                      +----+
15  |rehashidx=1 | \   |used=3     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+   +----+
19                     |dictEntry**|--+  |dictEntry*|-->|K1|V|->NULL
20                     +-----------+     +----------+   +----+ 
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+
23                     |sizemask=7 |     |dictEntry*|->NULL
24                     +-----------+     +----------+
25                     |used=1     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+
29                                       |dictEntry*|->NULL
30                                       +----------+
31                                       |dictEntry*|->NULL
32                                       +----------+
33                                       |dictEntry*|->NULL
34                                       +----------+
35 */

所有節點移完時:

 1 /*
 2                                                        
 3                                                   
 4                                    +->+----------+
 5                                    |  |dictEntry*|->NULL
 6                                    |  +----------+
 7                                    |  |dictEntry*|->NULL    
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|->NULL    
10  +------------+  /  +-----------+     +----------+
11  |privdata    | /   |size=4     |     |dictEntry*|->NULL    
12  +------------+/    +-----------+     +----------+ 
13  |ht[2]       |     |sizemask=3 |                  
14  +------------+\    +-----------+                      
15  |rehashidx=4 | \   |used=0     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+   +----+
19                     |dictEntry**|--+  |dictEntry*|-->|K1|V|->NULL
20                     +-----------+     +----------+   +----+ 
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+   +----+
23                     |sizemask=7 |     |dictEntry*|-->|K3|V|->NULL
24                     +-----------+     +----------+   +----+
25                     |used=4     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+   +----+
29                                       |dictEntry*|-->|K2|V|->NULL
30                                       +----------+   +----+
31                                       |dictEntry*|->NULL
32                                       +----------+   +----+
33                                       |dictEntry*|-->|K4|V|->NULL
34                                       +----------+   +----+ 
35 */

此時ht[0]->used為0,釋放原ht[0]的hash table,把ht[1]賦值給ht[0],并設定ht[1] = NULL,最后重置rehashidx=-1,rehashing操作結束

 1 /*
 2  +------------+    /+-----------+   +-->+----------+   +----+
 3  |dictType*   |   / |dictEntry**|---+   |dictEntry*|-->|K1|V|->NULL
 4  +------------+  /  +-----------+       +----------+   +----+
 5  |privdata    | /   |size=8     |       |dictEntry*|->NULL
 6  +------------+/    +-----------+       +----------+   +----+
 7  |ht[2]       |     |sizemask=7 |       |dictEntry*|-->|K3|V|->NULL
 8  +------------+\    +-----------+       +----------+   +----+
 9  |rehashidx=-1| \   |used=4     |       |dictEntry*|->NULL
10  +------------+  \  +-----------+       +----------+
11  |iterators=0 |   \                     |dictEntry*|->NULL
12  +------------+    \+-----------+       +----------+   +----+
13                     |dictEntry**|->NULL |dictEntry*|-->|K2|V|->NULL
14                     +-----------+       +----------+   +----+
15                     |size=0     |       |dictEntry*|->NULL
16                     +-----------+       +----------+   +----+
17                     |sizemask=0 |       |dictEntry*|-->|K4|V|->NULL
18                     +-----------+       +----------+   +----+
19                     |used=0     |     
20                     +-----------+     
21 */

rehashing操作的觸發共有兩種方式

1、定時操作

 1 long long timeInMilliseconds(void) {
 2     struct timeval tv;
 3 
 4     gettimeofday(&tv,NULL);
 5     return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000);
 6 }
 7 
 8 /* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */
 9 int dictRehashMilliseconds(dict *d, int ms) {
10     long long start = timeInMilliseconds();
11     int rehashes = 0;
12 
13     while(dictRehash(d,100)) {
14         rehashes += 100;
15         if (timeInMilliseconds()-start > ms) break;
16     }
17     return rehashes;
18 }

 

外部傳入一個毫秒時間,在這時間內回圈執行rehashing,每次執行100次,

2、操作時觸發

1 static void _dictRehashStep(dict *d) {
2     if (d->iterators == 0) dictRehash(d,1);
3 }

在插入、洗掉、查找等操作時,順帶執行一次rehashing操作,值得注意的是,如果存在安全的迭代器,即d->iterators != 0,則不會進行rehashing操作

 

三、插入

獲取可插入新節點的bucket idx的方法:

 1 static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
 2 {
 3     unsigned long idx, table;
 4     dictEntry *he;
 5     if (existing) *existing = NULL;
 6 
 7     /* Expand the hash table if needed */
 8     if (_dictExpandIfNeeded(d) == DICT_ERR)
 9         return -1;
10     for (table = 0; table <= 1; table++) {
11         idx = hash & d->ht[table].sizemask;
12         /* Search if this slot does not already contain the given key */
13         he = d->ht[table].table[idx];
14         while(he) {
15             if (key==he->key || dictCompareKeys(d, key, he->key)) {
16                 if (existing) *existing = he;
17                 return -1;
18             }
19             he = he->next;
20         }
21         if (!dictIsRehashing(d)) break;
22     }
23     return idx;
24 }

此方法在進行查找idx之前,先進行一次判斷,是否需要rehashing操作,而后進行查找,idx的值就是通過hash函式計算出來的hash_value與sizemask做位運算的結果,然后遍歷此idx對應的bucket,若已存在相同的key,則認為不可插入,并把對應的dictEntry用傳入的二級指標的方式傳出,供呼叫者使用,若不存在,則需要判斷是否正在進行rehashing操作,若在,則會對ht[1]做一次相同的操作,最終可以得到一個idx值,或傳出一個dictEntry,

由于rehashing期間,將會把ht[0]的所有dictEntry依次轉移至ht[1],為了防止新插入的dictEntry落到ht[0]已完成rehashing操作的bucket上,在rehashing期間,回傳的可插入的idx一定是屬于ht[1]的,

插入方法:

 1 dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
 2 {
 3     long index;
 4     dictEntry *entry;
 5     dictht *ht;
 6 
 7     if (dictIsRehashing(d)) _dictRehashStep(d);
 8 
 9     /* Get the index of the new element, or -1 if
10      * the element already exists. */
11     if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
12         return NULL;
13 
14     /* Allocate the memory and store the new entry.
15      * Insert the element in top, with the assumption that in a database
16      * system it is more likely that recently added entries are accessed
17      * more frequently. */
18     ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
19     entry = zmalloc(sizeof(*entry));
20     entry->next = ht->table[index];
21     ht->table[index] = entry;
22     ht->used++;
23 
24     /* Set the hash entry fields. */
25     dictSetKey(d, entry, key);
26     return entry;
27 }

若不存在相同key,則插入,否則,傳出dictEntry的指標,插入時,由于沒有記錄每個dictEntry鏈表的尾指標,所以使用頭插法,可以節約插入時的時間消耗,

dictAddRaw做為最終插入的方法,被多個方法所呼叫:

 1 //若不存在,則插入,否則報錯
 2 int dictAdd(dict *d, void *key, void *val)
 3 {
 4     dictEntry *entry = dictAddRaw(d,key,NULL);
 5 
 6     if (!entry) return DICT_ERR;
 7     dictSetVal(d, entry, val);
 8     return DICT_OK;
 9 }
10 
11 //若存在,則替換value,否則插入
12 int dictReplace(dict *d, void *key, void *val)
13 {
14     dictEntry *entry, *existing, auxentry;
15     entry = dictAddRaw(d,key,&existing);
16     if (entry) {
17         dictSetVal(d, entry, val);
18         return 1;
19     }
20     auxentry = *existing;
21     dictSetVal(d, existing, val);
22     dictFreeVal(d, &auxentry);
23     return 0;
24 }
25 
26 //若存在,則回傳對應dictEntry,否則插入后回傳新的dictEntry
27 dictEntry *dictAddOrFind(dict *d, void *key) {
28     dictEntry *entry, *existing;
29     entry = dictAddRaw(d,key,&existing);
30     return entry ? entry : existing;
31 }

對于一個剛剛create的dict:

 1 /*
 2                                                                            
 3 +------------+    /+-----------+  
 4 |dictType*   |   / |dictEntry**|-->NULL
 5 +------------+  /  +-----------+     
 6 |privdata    | /   |size=0     |     
 7 +------------+/    +-----------+     
 8 |ht[2]       |     |sizemask=0 |     
 9 +------------+\    +-----------+     
10 |rehashidx=-1| \   |used=0     |     
11 +------------+  \  +-----------+     
12 |iterators=0 |   \                   
13 +------------+    \+-----------+                      
14                    |dictEntry**|-->NULL
15                    +-----------+
16                    |size=0     |
17                    +-----------+
18                    |sizemask=0 |
19                    +-----------+
20                    |used=0     |
21                    +-----------+
22 */

假設K1、K2、K3、K4計算出來的hash值分別為0、5、2、7,使用sizemask計算出來的idx分別為0、1、2、3

現呼叫dictAdd方法進行插入

1、插入K1

執行完dictAddRaw中的_dictKeyIndex里的_dictExpandIfNeeded:

 1 /*
 2                                                       
 3                                                    +-->NULL
 4 +------------+    /+-----------+  +->+----------+ /   
 5 |dictType*   |   / |dictEntry**|--+  |dictEntry*|/    
 6 +------------+  /  +-----------+     +----------+ +--->NULL
 7 |privdata    | /   |size=4     |     |dictEntry*|/    
 8 +------------+/    +-----------+     +----------+
 9 |ht[2]       |     |sizemask=3 |     |dictEntry*|\    
10 +------------+\    +-----------+     +----------+ +--->NULL
11 |rehashidx=-1| \   |used=0     |     |dictEntry*|\    
12 +------------+  \  +-----------+     +----------+ \   
13 |iterators=0 |   \                                 +-->NULL
14 +------------+    \+-----------+                      
15                    |dictEntry**|-->NULL
16                    +-----------+
17                    |size=0     |
18                    +-----------+
19                    |sizemask=0 |
20                    +-----------+
21                    |used=0     |
22                    +-----------+
23 */ 

同時得到其在ht[0]的idx = 0,且不在rehashing操作中,于是直接插入

 1 /*
 2                                                       +----+
 3                                                    +->|K1|V|->NULL
 4 +------------+    /+-----------+  +->+----------+ /   +----+
 5 |dictType*   |   / |dictEntry**|--+  |dictEntry*|/    
 6 +------------+  /  +-----------+     +----------+ +--->NULL
 7 |privdata    | /   |size=4     |     |dictEntry*|/    
 8 +------------+/    +-----------+     +----------+
 9 |ht[2]       |     |sizemask=3 |     |dictEntry*|\    
10 +------------+\    +-----------+     +----------+ +--->NULL
11 |rehashidx=-1| \   |used=1     |     |dictEntry*|\    
12 +------------+  \  +-----------+     +----------+ \   
13 |iterators=0 |   \                                 +-->NULL
14 +------------+    \+-----------+                      
15                    |dictEntry**|-->NULL
16                    +-----------+
17                    |size=0     |
18                    +-----------+
19                    |sizemask=0 |
20                    +-----------+
21                    |used=0     |
22                    +-----------+
23 */

 2、插入K2、K3、K4后:

 1 /*
 2                                                       +----+
 3                                                    +->|K1|V|->NULL
 4 +------------+    /+-----------+  +->+----------+ /   +----+
 5 |dictType*   |   / |dictEntry**|--+  |dictEntry*|/    +-----
 6 +------------+  /  +-----------+     +----------+ +-->|K2|V|->NULL
 7 |privdata    | /   |size=4     |     |dictEntry*|/    +----+
 8 +------------+/    +-----------+     +----------+
 9 |ht[2]       |     |sizemask=3 |     |dictEntry*|\    +----+
10 +------------+\    +-----------+     +----------+ +-->|K3|V|->NULL
11 |rehashidx=-1| \   |used=4     |     |dictEntry*|\    +----+
12 +------------+  \  +-----------+     +----------+ \   +----+
13 |iterators=0 |   \                                 +->|K4|V|->NULL
14 +------------+    \+-----------+                      +----+
15                    |dictEntry**|-->NULL
16                    +-----------+
17                    |size=0     |
18                    +-----------+
19                    |sizemask=0 |
20                    +-----------+
21                    |used=0     |
22                    +-----------+
23 */

3、此時若有一個K5,計算出來的hash值為8,則:

i.因此刻不在rehashing操作,所以不用做處理

ii.執行完dictAddRaw中的_dictKeyIndex里的_dictExpandIfNeeded:

 1 /*
 2                                                        +----+
 3                                                     +->|K1|V|->NULL
 4                                    +->+----------+ /   +----+
 5                                    |  |dictEntry*|/    +----+
 6                                    |  +----------+ +-->|K2|V|->NULL
 7                                    |  |dictEntry*|/    +----+
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
10  +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
11  |privdata    | /   |size=4     |     |dictEntry*|\    +----+
12  +------------+/    +-----------+     +----------+ \   +----+
13  |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
14  +------------+\    +-----------+                      +----+
15  |rehashidx=0 | \   |used=4     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+   
19                     |dictEntry**|--+  |dictEntry*|->NULL
20                     +-----------+     +----------+   
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+
23                     |sizemask=7 |     |dictEntry*|->NULL
24                     +-----------+     +----------+
25                     |used=0     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+
29                                       |dictEntry*|->NULL
30                                       +----------+
31                                       |dictEntry*|->NULL
32                                       +----------+
33                                       |dictEntry*|->NULL
34                                       +----------+
35 */

同時得到其在ht[1]的idx=0

iii.插入

 1 /*
 2                                                        +----+
 3                                                     +->|K1|V|->NULL
 4                                    +->+----------+ /   +----+
 5                                    |  |dictEntry*|/    +----+
 6                                    |  +----------+ +-->|K2|V|->NULL
 7                                    |  |dictEntry*|/    +----+
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
10  +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
11  |privdata    | /   |size=4     |     |dictEntry*|\    +----+
12  +------------+/    +-----------+     +----------+ \   +----+
13  |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
14  +------------+\    +-----------+                      +----+
15  |rehashidx=0 | \   |used=4     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+   +----+
19                     |dictEntry**|--+  |dictEntry*|-->|K5|V|->NULL
20                     +-----------+     +----------+   +----+ 
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+
23                     |sizemask=7 |     |dictEntry*|->NULL
24                     +-----------+     +----------+
25                     |used=1     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+
29                                       |dictEntry*|->NULL
30                                       +----------+
31                                       |dictEntry*|->NULL
32                                       +----------+
33                                       |dictEntry*|->NULL
34                                       +----------+
35 */

4、此時若有一個K6,計算出來的hash值為16,則:

i.此時已處理rehashing操作,執行一步:

 1 /*
 2                                                        
 3                                                     +-->NULL
 4                                    +->+----------+ /   
 5                                    |  |dictEntry*|/    +----+
 6                                    |  +----------+ +-->|K2|V|->NULL
 7                                    |  |dictEntry*|/    +----+
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
10  +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
11  |privdata    | /   |size=4     |     |dictEntry*|\    +----+
12  +------------+/    +-----------+     +----------+ \   +----+
13  |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
14  +------------+\    +-----------+                      +----+
15  |rehashidx=1 | \   |used=3     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+   +----+  +----+
19                     |dictEntry**|--+  |dictEntry*|-->|K1|V|->|K5|V|->NULL
20                     +-----------+     +----------+   +----+  +----+
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+
23                     |sizemask=7 |     |dictEntry*|->NULL
24                     +-----------+     +----------+
25                     |used=2     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+
29                                       |dictEntry*|->NULL
30                                       +----------+
31                                       |dictEntry*|->NULL
32                                       +----------+
33                                       |dictEntry*|->NULL
34                                       +----------+
35 */

ii.執行完dictAddRaw中的_dictKeyIndex里的_dictExpandIfNeeded,因已在進行rehashing,所以不做任何處理,只回傳其在ht[1]的idx 0

iii.頭插法將K6插入

 1 /*
 2                                                        
 3                                                     +-->NULL
 4                                    +->+----------+ /   
 5                                    |  |dictEntry*|/    +----+
 6                                    |  +----------+ +-->|K2|V|->NULL
 7                                    |  |dictEntry*|/    +----+
 8  +------------+    /+-----------+  |  +----------+
 9  |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
10  +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
11  |privdata    | /   |size=4     |     |dictEntry*|\    +----+
12  +------------+/    +-----------+     +----------+ \   +----+
13  |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
14  +------------+\    +-----------+                      +----+
15  |rehashidx=1 | \   |used=3     | 
16  +------------+  \  +-----------+  
17  |iterators=0 |   \                
18  +------------+    \+-----------+  +->+----------+   +----+  +----+  +----+
19                     |dictEntry**|--+  |dictEntry*|-->|K6|V|->|K1|V|->|K5|V|->NULL
20                     +-----------+     +----------+   +----+  +----+  +----+
21                     |size=8     |     |dictEntry*|->NULL
22                     +-----------+     +----------+
23                     |sizemask=7 |     |dictEntry*|->NULL
24                     +-----------+     +----------+
25                     |used=3     |     |dictEntry*|->NULL
26                     +-----------+     +----------+
27                                       |dictEntry*|->NULL
28                                       +----------+
29                                       |dictEntry*|->NULL
30                                       +----------+
31                                       |dictEntry*|->NULL
32                                       +----------+
33                                       |dictEntry*|->NULL
34                                       +----------+
35 */

 以上為正常插入時的情況,key已存在,或是呼叫另外兩個方法的情況與之大同小異,不再做過多敘述,

 

四、查找 

 1 dictEntry *dictFind(dict *d, const void *key)
 2 {
 3     dictEntry *he;
 4     uint64_t h, idx, table;
 5 
 6     if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
 7     if (dictIsRehashing(d)) _dictRehashStep(d);
 8     h = dictHashKey(d, key);
 9     for (table = 0; table <= 1; table++) {
10         idx = h & d->ht[table].sizemask;
11         he = d->ht[table].table[idx];
12         while(he) {
13             if (key==he->key || dictCompareKeys(d, key, he->key))
14                 return he;
15             he = he->next;
16         }
17         if (!dictIsRehashing(d)) return NULL;
18     }
19     return NULL;
20 }

同樣,若在rehashing期間,則執行一次,首先在ht[0]里查找,計算出hash值對應ht[0]的idx,取得其bucket,然后遍歷之,找到與指定key相同的dictEntry,若ht[0]中找不到指定的key,且正在進行rehashing操作,則去ht[1]以相同方式也查找一次,

redis額外提供一個,根據key只獲取其value的方法:

1 void *dictFetchValue(dict *d, const void *key) {
2     dictEntry *he;
3 
4     he = dictFind(d,key);
5     return he ? dictGetVal(he) : NULL;
6 }

key不存在時回傳NULL

 

五、洗掉

洗掉方法:

 1 static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
 2     uint64_t h, idx;
 3     dictEntry *he, *prevHe;
 4     int table;
 5 
 6     if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;
 7 
 8     if (dictIsRehashing(d)) _dictRehashStep(d);
 9     h = dictHashKey(d, key);
10 
11     for (table = 0; table <= 1; table++) {
12         idx = h & d->ht[table].sizemask;
13         he = d->ht[table].table[idx];
14         prevHe = NULL;
15         while(he) {
16             if (key==he->key || dictCompareKeys(d, key, he->key)) {
17                 /* Unlink the element from the list */
18                 if (prevHe)
19                     prevHe->next = he->next;
20                 else
21                     d->ht[table].table[idx] = he->next;
22                 if (!nofree) {
23                     dictFreeKey(d, he);
24                     dictFreeVal(d, he);
25                     zfree(he);
26                 }
27                 d->ht[table].used--;
28                 return he;
29             }
30             prevHe = he;
31             he = he->next;
32         }
33         if (!dictIsRehashing(d)) break;
34     }
35     return NULL; /* not found */
36 }

查找方式與dictFind相同,找到之后,由呼叫者指定是否要銷毀此dictEntry,若不銷毀,則要把對應指標傳出來,給外部使用,

此方法被兩個介面所呼叫:

1 int dictDelete(dict *ht, const void *key) {
2     return dictGenericDelete(ht,key,0) ? DICT_OK : DICT_ERR;
3 }
4 
5 dictEntry *dictUnlink(dict *ht, const void *key) {
6     return dictGenericDelete(ht,key,1);
7 }

dictDelete就不用多說了,直接洗掉對應dictEntry,關于為什么需要dictUnlink,原始碼的注釋上寫道,如果有某種操作,需要先查找指定key對應的dictEntry,然后對其做點操作,接著就直接洗掉,在沒有dictUnlink的時候,需要這樣:

1 entry = dictFind(...);
2 // Do something with entry
3 dictDelete(dictionary,entry);

實際需要查找兩次,而在有dictUnlink的情況下:

1 entry = dictUnlink(dictionary,entry);
2 // Do something with entry
3 dictFreeUnlinkedEntry(entry); 

只需要一次查找,配合專門的洗掉操作,即可,

1 void dictFreeUnlinkedEntry(dict *d, dictEntry *he) {
2     if (he == NULL) return;
3     dictFreeKey(d, he);
4     dictFreeVal(d, he);
5     zfree(he);
6 }

 

六、銷毀

清空一個hash table的方法

 1 int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
 2     unsigned long i;
 3 
 4     /* Free all the elements */
 5     for (i = 0; i < ht->size && ht->used > 0; i++) {
 6         dictEntry *he, *nextHe;
 7 
 8         if (callback && (i & 65535) == 0) callback(d->privdata);
 9 
10         if ((he = ht->table[i]) == NULL) continue;
11         while(he) {
12             nextHe = he->next;
13             dictFreeKey(d, he);
14             dictFreeVal(d, he);
15             zfree(he);
16             ht->used--;
17             he = nextHe;
18         }
19     }
20     /* Free the table and the allocated cache structure */
21     zfree(ht->table);
22     /* Re-initialize the table */
23     _dictReset(ht);
24     return DICT_OK; /* never fails */
25 }

兩層回圈,分別遍歷所有bucket與單bucket里所有dictEntry進行釋放,關于這里的 (i&65535) == 0的判斷,_dictClear方法僅在相同檔案的方法dictEmpty與dictRelease呼叫

 1 void dictRelease(dict *d)
 2 {
 3     _dictClear(d,&d->ht[0],NULL);
 4     _dictClear(d,&d->ht[1],NULL);
 5     zfree(d);
 6 }
 7 
 8 void dictEmpty(dict *d, void(callback)(void*)) {
 9     _dictClear(d,&d->ht[0],callback);
10     _dictClear(d,&d->ht[1],callback);
11     d->rehashidx = -1;
12     d->iterators = 0;
13 }

dictRelease不用多說,傳入的callback為NULL,而dictEmpty,搜索redis原始碼所有檔案的呼叫,

ccx@ccx:~/Desktop/redis-5.0.7/redis-5.0.7/src$ grep dictEmpty * -r          
blocked.c:    dictEmpty(c->bpop.keys,NULL);
db.c:            dictEmpty(server.db[j].dict,callback);
db.c:            dictEmpty(server.db[j].expires,callback);
dict.c:void dictEmpty(dict *d, void(callback)(void*)) {
dict.h:void dictEmpty(dict *d, void(callback)(void*));
replication.c:    dictEmpty(server.repl_scriptcache_dict,NULL);
sentinel.c:    dictEmpty(server.commands,NULL);

僅db.c里傳了callback進來,對應的方法為

1 long long emptyDb(int dbnum, int flags, void(callback)(void*));

繼續搜索:

ccx@ccx:~/Desktop/redis-5.0.7/redis-5.0.7/src$ grep emptyDb * -r
cluster.c:        emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
db.c:long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
db.c:            emptyDbAsync(&server.db[j]);
db.c:/* Return the set of flags to use for the emptyDb() call for FLUSHALL*/
db.c:    server.dirty += emptyDb(c->db->id,flags,NULL);
db.c:    server.dirty += emptyDb(-1,flags,NULL);
debug.c:        emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
debug.c:        emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
lazyfree.c:void emptyDbAsync(redisDb *db) {
replication.c: * data with emptyDb(), and while we load the new data received as an
replication.c:/* Callback used by emptyDb() while flushing away old data to load*/
replication.c:        emptyDb(
server.h:long long emptyDb(int dbnum, int flags, void(callback)(void*));
server.h:void emptyDbAsync(redisDb *db);

 真正呼叫的地方傳入的也是NULL,并不知道是拿來做什么用的...

ps:用grep查找只是方便cv過來....

 

七、迭代器操作

 資料結構:

1 typedef struct dictIterator {
2     dict *d;
3     long index;
4     int table, safe;
5     dictEntry *entry, *nextEntry;
6     /* unsafe iterator fingerprint for misuse detection. */
7     long long fingerprint;
8 } dictIterator;

根據原始碼注釋可知,如果是個安全的迭代器,即safe == 1,則在迭代中可以呼叫dictAdd、dictFind等方法,否則只能呼叫dictNext,

index表示,ht[table]對應的bucket的idx,

獲取迭代器:

 1 dictIterator *dictGetIterator(dict *d)
 2 {
 3     dictIterator *iter = zmalloc(sizeof(*iter));
 4 
 5     iter->d = d;
 6     iter->table = 0;
 7     iter->index = -1;
 8     iter->safe = 0;
 9     iter->entry = NULL;
10     iter->nextEntry = NULL;
11     return iter;
12 }
13 
14 dictIterator *dictGetSafeIterator(dict *d) {
15     dictIterator *i = dictGetIterator(d);
16 
17     i->safe = 1;
18     return i;
19 }

付訓取的迭代器并不指向具體哪個dictEntry

next操作:

 1 dictEntry *dictNext(dictIterator *iter)
 2 {
 3     while (1) {
 4         if (iter->entry == NULL) {
 5             dictht *ht = &iter->d->ht[iter->table];
 6             if (iter->index == -1 && iter->table == 0) {
 7                 if (iter->safe)
 8                     iter->d->iterators++;
 9                 else
10                     iter->fingerprint = dictFingerprint(iter->d);
11             }
12             iter->index++;
13             if (iter->index >= (long) ht->size) {
14                 if (dictIsRehashing(iter->d) && iter->table == 0) {
15                     iter->table++;
16                     iter->index = 0;
17                     ht = &iter->d->ht[1];
18                 } else {
19                     break;
20                 }
21             }
22             iter->entry = ht->table[iter->index];
23         } else {
24             iter->entry = iter->nextEntry;
25         }
26         if (iter->entry) {
27             /* We need to save the 'next' here, the iterator user
28              * may delete the entry we are returning. */
29             iter->nextEntry = iter->entry->next;
30             return iter->entry;
31         }
32     }
33     return NULL;
34 }

對于一個新的迭代器,首次呼叫時,會根據是否安全,做不同操作,安全的迭代器會給dict里的計數器+1,不安全的將會記錄本字典的指紋,之后會遍歷ht[0],取到第一個非NULL的dictEntry,當ht[0]遍歷完且取不到非NULL的dictEntry時,如果正在進行rehashing操作,則會去ht[1]里找,

如:

 1 /*
 2 
 3      +-------------------------+
 4 +----|dict *                   |
 5 |    +-------------------------+
 6 |    |long index               |
 7 |    +-------------------------+
 8 |    |int table                |
 9 |    +-------------------------+
10 |    |int safe                 |
11 |    +-------------------------+
12 |    |dictEntry *entry         |->NULL
13 |    +-------------------------+
14 |    |dictEntry *entrynextEntry|->NULL
15 |    +-------------------------+
16 |    |long long fingerprint    |
17 |    +-------------------------+
18 |
19 |
20 |                                                      
21 |                                                       +-->NULL
22 |                                      +->+----------+ /   
23 |                                      |  |dictEntry*|/    +----+
24 |                                      |  +----------+ +-->|K2|V|->NULL
25 |                                      |  |dictEntry*|/    +----+
26 +--->+------------+    /+-----------+  |  +----------+
27      |dictType*   |   / |dictEntry**|--+  |dictEntry*|\    +----+
28      +------------+  /  +-----------+     +----------+ +-->|K3|V|->NULL
29      |privdata    | /   |size=4     |     |dictEntry*|\    +----+
30      +------------+/    +-----------+     +----------+ \   +----+
31      |ht[2]       |     |sizemask=3 |                   +->|K4|V|->NULL
32      +------------+\    +-----------+                      +----+
33      |rehashidx=1 | \   |used=3     | 
34      +------------+  \  +-----------+  
35      |iterators=0 |   \                
36      +------------+    \+-----------+  +->+----------+   +----+  +----+
37                         |dictEntry**|--+  |dictEntry*|-->|K1|V|->|K5|V|->NULL
38                         +-----------+     +----------+   +----+  +----+
39                         |size=8     |     |dictEntry*|->NULL
40                         +-----------+     +----------+
41                         |sizemask=7 |     |dictEntry*|->NULL
42                         +-----------+     +----------+
43                         |used=3     |     |dictEntry*|->NULL
44                         +-----------+     +----------+
45                                           |dictEntry*|->NULL
46                                           +----------+
47                                           |dictEntry*|->NULL
48                                           +----------+
49                                           |dictEntry*|->NULL
50                                           +----------+
51                                           |dictEntry*|->NULL
52                                           +----------+
53 */

遍歷順序為,K2,K3,K4,K1,K5,

迭代器銷毀:

 1 void dictReleaseIterator(dictIterator *iter)
 2 {
 3     if (!(iter->index == -1 && iter->table == 0)) {
 4         if (iter->safe)
 5             iter->d->iterators--;
 6         else
 7             assert(iter->fingerprint == dictFingerprint(iter->d));
 8     }
 9     zfree(iter);
10 }

與首次執行next操作相對應,若為safe的迭代器,要給dict的計算減1,否則要校驗期間dict的指紋是否發生了變化 ,

指紋的計算:

 1 long long dictFingerprint(dict *d) {
 2     long long integers[6], hash = 0;
 3     int j;
 4 
 5     integers[0] = (long) d->ht[0].table;
 6     integers[1] = d->ht[0].size;
 7     integers[2] = d->ht[0].used;
 8     integers[3] = (long) d->ht[1].table;
 9     integers[4] = d->ht[1].size;
10     integers[5] = d->ht[1].used;
11 
12     /* We hash N integers by summing every successive integer with the integer
13      * hashing of the previous sum. Basically:
14      *
15      * Result = hash(hash(hash(int1)+int2)+int3) ...
16      *
17      * This way the same set of integers in a different order will (likely) hash
18      * to a different number. */
19     for (j = 0; j < 6; j++) {
20         hash += integers[j];
21         /* For the hashing step we use Tomas Wang's 64 bit integer hash. */
22         hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1;
23         hash = hash ^ (hash >> 24);
24         hash = (hash + (hash << 3)) + (hash << 8); // hash * 265
25         hash = hash ^ (hash >> 14);
26         hash = (hash + (hash << 2)) + (hash << 4); // hash * 21
27         hash = hash ^ (hash >> 28);
28         hash = hash + (hash << 31);
29     }
30     return hash;
31 }

對于不安全的迭代器,在迭代程序中,不允許操作任何修改dict的操作,是只讀的,不會發生迭代器失效的問題,對于安全的迭代器,在進行操作本節點的時候,redis中記錄了當前迭代的bucket idx,以及當前dictEntry的next節點,如果只是add操作,即使是用了頭插法把新dictEntry插在本節點之前,對迭代器本身并沒有影響,如果是delete了本節點,迭代器中還記錄了next節點的位置,呼叫next時直接取就好,如果next為空,則可以認為當前bucket遍歷完了,取下一個bucket就行了,當然,如果在add/delete等操作的時候,進行了rehashing操作,那么當前迭代器里記錄的next,在rehashing之后,可能就不是當前節點新位置的next了,所以在使用安全迭代器的時候,禁止了rehashing操作,

 

八、其它操作

dict還支持其它的一些操作,

1、隨機獲取一個key,可以用于一些隨機操作的dictGetRandomKey

2、隨機獲取n個key:dictGetSomeKeys

3、scan操作

關于scan操作,redis采用了一個很巧妙的方法,保證了在開始scan時未洗掉的元素一定能遍歷到,又能保證盡量少地重復遍歷,

這里直接給個傳送門,這里講得很好:

https://blog.csdn.net/gqtcgq/article/details/50533336

 

redis 5.0.7 下載鏈接

http://download.redis.io/releases/redis-5.0.7.tar.gz

原始碼閱讀順序參考:

https://github.com/huangz1990/blog/blob/master/diary/2014/how-to-read-redis-source-code.rst

其它參考:

https://zhuanlan.zhihu.com/p/42156903

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/22942.html

標籤:NoSQL

上一篇:Oracle 10g在創建資料庫中,不能通過Enterprise Manager進行管理

下一篇:oracle啟動報錯求助

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more