libevent中,須要將大量的監聽事件event進行歸類存放,好比一個文件描述符fd可能對應多個監聽事件,對大量的事件event採用監聽的所採用的數據結構是event_io_map,其實現經過哈希表,本文分析這種哈希結構的實現。node
既然是哈希結構,從全局上必然一個是鍵key,一個是value,libevent的哈希結構中,主要實現的是文件描述符fd(key)到該文件描述符fd所關聯的事件event(value)之間的映射。linux
有一點須要說明的是,該哈希結構只在在windows平臺下使用,其餘如linux平臺下不使用該哈希結構windows
#ifdef WIN32 #define EVMAP_USE_HT #endif #ifdef EVMAP_USE_HT #include "ht-internal.h" struct event_map_entry; HT_HEAD(event_io_map, event_map_entry); //哈希表頭部 #else #define event_io_map event_signal_map #endif
能夠看到若是是非win32平臺,event_io_map就被定義爲一個很簡單的結構event_signal_map。雖然咱們大多狀況下在linux平臺下使用libevent,可是不妨礙咱們學習這一哈希結構。數組
這裏只有在win32平臺下使用哈希結構的緣由是:由於在Windows系統裏面,文件描述符是一個比較大的值,不適合放到event_signal_map結構中。而經過哈希(模上一個小的值),就能夠變得比較小,這樣就能夠放到哈希表的數組中了。而遵循POSIX標準的文件描述符是從0開始遞增的,通常都不會太大,適用於event_signal_map。數據結構
下面着重win32平臺下的哈希結構:socket
struct evmap_io { struct event_list events; //libevent支持對於同一個文件描述符fd添加多個event,這些event掛在鏈表中 ev_uint16_t nread; ev_uint16_t nwrite; }; struct event_map_entry { HT_ENTRY(event_map_entry) map_node; //next指針,用於解決地址衝突的下一個event_map_entry evutil_socket_t fd; //文件描述符 union { /* This is a union in case we need to make more things that can be in the hashtable. */ struct evmap_io evmap_io;// } ent; }; //用宏定義實現的哈希結構 #define HT_HEAD(name, type) \ struct name { \ /* The hash table itself. */ \ struct type **hth_table; \ /* How long is the hash table? */ \ unsigned hth_table_length; \ /* How many elements does the table contain? */ \ unsigned hth_n_entries; \ /* How many elements will we allow in the table before resizing it? */ \ unsigned hth_load_limit; \ /* Position of hth_table_length in the primes table. */ \ int hth_prime_idx; \ }
上面的哈希表結構用宏定義實現的,主要包含的字段你們看英文註釋也基本能理解,實際對應上面的數據結構應該是函數
struct evmap_io_map { struct event_map_entry **hth_table; unsigned hth_table_length; unsigned hth_n_entries; unsigned hth_load_limit; int hth_prime_idx; };
下面的示意圖展現了哈希表的結構:
學習
哈希表採用鏈地址解決地址衝突問題,所以掛在同一個哈希表單元下的event_map_entry結構不必定有着相同的fd,它們只是計算出相同的哈希值,計算哈希值(也就是哈希表下標)的採用取模的方法(後面會提到)測試
清楚了該哈希結構以後,剩下的就是屬於哈希表操做的部分,該部分主要在ht-internal.h文件中,哈希操做實現所有采用宏定義
定義的哈希結構操做ui
#define HT_FIND(name, head, elm) name##_HT_FIND((head), (elm)) #define HT_INSERT(name, head, elm) name##_HT_INSERT((head), (elm)) #define HT_REPLACE(name, head, elm) name##_HT_REPLACE((head), (elm)) #define HT_REMOVE(name, head, elm) name##_HT_REMOVE((head), (elm)) #define HT_START(name, head) name##_HT_START(head) #define HT_NEXT(name, head, elm) name##_HT_NEXT((head), (elm)) #define HT_NEXT_RMV(name, head, elm) name##_HT_NEXT_RMV((head), (elm)) #define HT_CLEAR(name, head) name##_HT_CLEAR(head) #define HT_INIT(name, head) name##_HT_INIT(head)
(1)查找
static inline struct type ** \ _##name##_HT_FIND_P(struct name *head, struct type *elm) \ { \ struct type **p; \ if (!head->hth_table) \ return NULL; \ p = &_HT_BUCKET(head, field, elm, hashfn); \ /*查找對應衝突鏈表尋找對應元素*/ while (*p) { \ if (eqfn(*p, elm)) \ return p; \ p = &(*p)->field.hte_next; \ } \ return p; \ } static inline struct type * \ name##_HT_FIND(const struct name *head, struct type *elm) \ { \ struct type **p; \ struct name *h = (struct name *) head; \ _HT_SET_HASH(elm, field, hashfn); \ p = _##name##_HT_FIND_P(h, elm); \ return p ? *p : NULL; \ }
查找操做定義了兩個函數(帶P的和不帶P的),帶P的函數返回的是查找到的元素(具體講就是event_map_entry*)的地址event_map_entry**,即便沒有查到對應的elm,
返回值p也不會爲空,這個指針對於哈希表的插入刪除操做頗有幫助(設想一下,若是隻有不帶P的查找函數,沒有查找到對應的elm返回NULL)
其中_HT_BUCKET(head, field, elm, hashfn)經過哈希值模哈希表的長度來獲得對應哈希表的下標,而後查找對應的衝突鏈表尋找對應的元素
#define _HT_BUCKET(head, field, elm, hashfn) \ ((head)->hth_table[_HT_ELT_HASH(elm,field,hashfn) % head->hth_table_length])
(2)插入
插入是首先判斷容量是否夠,若不夠就增長容量,增長容量的方式跟STL中的哈希表相似,整個哈希表數組大小的遞增以一組質數爲參考
static inline void \ name##_HT_INSERT(struct name *head, struct type *elm) \ { \ struct type **p; \ if (!head->hth_table || head->hth_n_entries >= head->hth_load_limit) \ name##_HT_GROW(head, head->hth_n_entries+1); \ ++head->hth_n_entries; \ _HT_SET_HASH(elm, field, hashfn); \ p = &_HT_BUCKET(head, field, elm, hashfn); \ elm->field.hte_next = *p; \ *p = elm; \ }
(3)替換
先查看哈希表中是否有該elm,若是沒有,插入衝突鏈表最後,返回NULL,若存在,替換該elem,並返回
舊的指針
static inline struct type * \ name##_HT_REPLACE(struct name *head, struct type *elm) \ { \ struct type **p, *r; \ if (!head->hth_table || head->hth_n_entries >= head->hth_load_limit) \ name##_HT_GROW(head, head->hth_n_entries+1); \ _HT_SET_HASH(elm, field, hashfn); \ p = _##name##_HT_FIND_P(head, elm); \ r = *p; \ *p = elm; \ if (r && (r!=elm)) { \ elm->field.hte_next = r->field.hte_next; \ r->field.hte_next = NULL; \ return r; \ } else { \ ++head->hth_n_entries; \ return NULL; \ } \ }
(4)刪除
將elem指定的元素刪除,基本的鏈表操做,很少說
static inline struct type * \ name##_HT_REMOVE(struct name *head, struct type *elm) \ { \ struct type **p, *r; \ _HT_SET_HASH(elm, field, hashfn); \ p = _##name##_HT_FIND_P(head,elm); \ if (!p || !*p) \ return NULL; \ r = *p; \ *p = r->field.hte_next; \ r->field.hte_next = NULL; \ --head->hth_n_entries; \ return r; \ }
以上是哈希表的基本操做,源碼中還有一些其餘的基本操做,只要理解了哈希表的結構,其餘的都是些鏈表的基本操做了。
最後是我的寫了一段測試代碼,對哈希表進行測試:
#include "ht-internal.h" #include <stdlib.h> #include <string.h> //須要存放在哈希表中的結構 struct map_entry { HT_ENTRY(map_entry) map_node; //爲解決地址衝突的next字段 unsigned key; //鍵 int value; //值 }; //哈希函數,對鍵不做處理 int hashfcn(struct map_entry *e) { return e->key; } //判斷兩個元素鍵是否相同,用於查找 int equalfcn(struct map_entry *e1, struct map_entry *e2) { return e1->key == e2->key; } //全局的哈希map static HT_HEAD(key_value_map, map_entry) g_map = HT_INITIALIZER(); //特例化對應的哈希表結構 HT_PROTOTYPE(key_value_map, map_entry, map_node, hashfcn, equalfcn) HT_GENERATE(key_value_map, map_entry, map_node, hashfcn, equalfcn, 0.5, malloc, realloc, free) //測試函數 void test() { struct map_entry elm1, elm2, elm3; elm1.key = 1; elm1.value = 19; elm1.map_node.hte_next = NULL; HT_INSERT(key_value_map, &g_map, &elm1); //第一次添加後哈希表的長度爲53,所以elm2與elm1產出地址衝突 elm2.key = 54; elm2.value = 48; elm2.map_node.hte_next = NULL; HT_INSERT(key_value_map, &g_map, &elm2); // elm3.key = 2; elm3.value = 14; elm3.map_node.hte_next = NULL; HT_INSERT(key_value_map, &g_map, &elm3); //打印哈希表中全部數據 for (unsigned b = 0; b < g_map.hth_table_length; b++) { if (g_map.hth_table[b]) { struct map_entry* elm = g_map.hth_table[b]; while (elm) { printf("b:%d key:%d value:%d\n", b, elm->key, elm->value); elm = elm->map_node.hte_next; } } } } int main() { test(); return 0; }
輸出結果: