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; }
输出结果: