libevent中evmap实现(哈希表)
libevent中,需要将大量的监听事件event进行归类存放,比如一个文件描述符fd可能对应多个监听事件,对大量的事件event采用监听的所采用的数据结构是event_io_map,其实现通过哈希表,本文分析这种哈希结构的实现。
既然是哈希结构,从全局上必然一个是键key,一个是value,libevent的哈希结构中,主要实现的是文件描述符fd(key)到该文件描述符fd所关联的事件event(value)之间的映射。
有一点需要说明的是,该哈希结构只在在windows平台下使用,其他如linux平台下不使用该哈希结构
#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平台下的哈希结构:
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文件中,哈希操作实现全部采用宏定义
定义的哈希结构操作
#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;
}
输出结果:

libevent中evmap实现(哈希表)的更多相关文章
- Java数据结构和算法(十三)——哈希表
Hash表也称散列表,也有直接译作哈希表,Hash表是一种根据关键字值(key - value)而直接进行访问的数据结构.它基于数组,通过把关键字映射到数组的某个下标来加快查找速度,但是又和数组.链表 ...
- 数据结构是哈希表(hashTable)
哈希表也称为散列表,是根据关键字值(key value)而直接进行访问的数据结构.也就是说,它通过把关键字值映射到一个位置来访问记录,以加快查找的速度.这个映射函数称为哈希函数(也称为散列函数),映射 ...
- 理解Golang哈希表Map的元素
目录 概述 哈希函数 冲突解决 初始化 结构体 字面量 运行时 操作 访问 写入 扩容 删除 总结 在上一节中我们介绍了 数组和切片的实现原理,这一节会介绍 Golang 中的另一个集合元素 - 哈希 ...
- c语言构建哈希表
/*哈希查找 *哈希函数的构造方法常用的有5种.分别是: *数字分析法 *平方取中法 *分段叠加 *伪随机数 *除留取余法 *这里面除留取余法比较常用 *避免哈希冲突常用的方法有4种: *开放定址法( ...
- Linux内核哈希表分析与应用
目录(?)[+] Linux内核哈希表分析与应用 Author:tiger-johnTime:2012-12-20mail:jibo.tiger@gmail.comBlog:http:// ...
- 哈希表(Hash Table)/散列表(Key-Value)
目录 1. 哈希表的基本思想 2. 哈希表的相关基本概念 1.概念: 2.哈希表和哈希函数的标准定义: 1)冲突: 2)安全避免冲突的条件: 3)冲突不可能完全避免 4)影响冲突的因素 3. 哈希表的 ...
- 操作系统 之 哈希表 Linux 内核 应用浅析
1.基本概念 散列表(Hash table.也叫哈希表).是依据关键码值(Key value)而直接进行訪问的数据结构. 也就是说,它通过把关键码值映射到表中一个位置来訪问记录.以 ...
- 哈希表 HashTable(又名散列表)
简介 其实通过标题上哈希表的英文名HashTable,我们就可以看出这是一个组合的数据结构Hash+Table. Hash是什么?它是一个函数,作用可以通过一个公式来表示: index = HashF ...
- Leetcode Lect7 哈希表
传统的哈希表 对于长度为n的哈希表,它的存储过程如下: 根据 key 计算出它的哈希值 h=hash(key) 假设箱子的个数为 n,那么这个键值对应该放在第 (h % n) 个箱子中 如果该箱子中已 ...
随机推荐
- Sping Boot入门到实战之实战篇(一):实现自定义Spring Boot Starter——阿里云消息队列服务Starter
在 Sping Boot入门到实战之入门篇(四):Spring Boot自动化配置 这篇中,我们知道Spring Boot自动化配置的实现,主要由如下几部分完成: @EnableAutoConfigu ...
- linux --> Linux变量之$#, $*, $@含义
Linux变量之$#, $*, $@含义 一.介绍 $# //是传给脚本的参数个数 $ //是脚本本身的名字 $1 //是传递给该shell脚本的第一个参数 $ //是传递给该shell脚本的第二个参 ...
- c++ --> 重载、重写(覆盖)和隐藏的区别
重载.重写(覆盖)和隐藏的区别 一.重载 重载从overload翻译过来,是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心 ...
- KVM之五:KVM日常管理常用命令
1.查看.编辑及备份KVM 虚拟机配置文件 以及查看KVM 状态: 1.1.KVM 虚拟机默认的配置文件在 /etc/libvirt/qemu 目录下,默认是以虚拟机名称命名的.xml 文件,如下,: ...
- (转)Android 仿订单出票效果 (附DEMO)
之前我下载了BaseAnimation 开源库(BaseAnimation是基于开源的APP,致力于收集各种动画效果) BaseAnimation 转载的链接:http://blog.csdn.net ...
- SpringMVC参数校验
使用SpringMVC时配合hibernate-validate进行参数的合法性校验,能节省一定的代码量. 使用步骤 1.搭建Web工程并引入hibernate-validate依赖 <depe ...
- (译文)学习ES6非常棒的特性-深入研究var, let and const
Var var firstVar; //firstVar被声明,它的默认值是undefined var secondVar = 2; //secondVar被声明,被赋值2 先看一个例子: var i ...
- 网络1711班 C语言第八次作业批改总结
网络1711班 C语言第七次作业批改总结 最近在忙一些琐事,没能及时批改大家的作业,连续两次作业总结也没有很用心写,在这要给大家say sorry. 1.本次作业评分细则 1.1 基本要求(1分) 按 ...
- JAVA使用和操作properties文件
java中的properties文件是一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式,在properti ...
- 同一个页面同时拥有collectionView和navigationBar和tabBar时可能遇到的问题
写一个页面的时候,遇到了页面加载时候collectionView的最下面少了49个像素的位置,切换去别的页面之后,再返回,又变回正常,多方求解无果后,发现原来是系统自带的适应功能导致的,加入以下代码即 ...