libevent源码学习(7):event_io_map——哈希表数据结构解析

目录

event_io_map

哈希表操作函数

hashcode与equals函数

哈希表初始化

哈希表元素查找

哈希表扩容

哈希表元素插入

哈希表元素替换

哈希表元素删除

自定义条件删除元素

哈希表第一个非空元素

哈希表下一个元素

释放哈希表

向event_io_map中添加event

激活event_io_map中的event

删除event_io_map中的event


以下源码均基于libevent-2.0.21-stable。

       在libevent中,自定义了一个哈希表结构用于实现event_io_map,该哈希表相关代码在ht-internal.h中,从该头文件的名称也可以看出来,libevent中的哈希表是一个并不对用户开放的内部使用结构。哈希表用于实现event_io_map,因此先来看看event_io_map的定义。

event_io_map

       在event_internal.h文件中,有如下定义:

#ifdef WIN32
/* If we're on win32, then file descriptors are not nice low densely packed
   integers.  Instead, they are pointer-like windows handles, and we want to
   use a hashtable instead of an array to map fds to events.
*/
#define EVMAP_USE_HT
#endif

/* #define HT_CACHE_HASH_VALS */

#ifdef EVMAP_USE_HT    //在windows下就是用哈希结构,否则直接使用event_signal_map
#include "ht-internal.h"
struct event_map_entry;
HT_HEAD(event_io_map, event_map_entry);  //定义一个结构体名为event_io_map,其中的哈希表元素类型为event_map_entry
#else
#define event_io_map event_signal_map
#endif

        根据上述定义可知,仅在Windows环境下,event_io_map会使用哈希结构,否则event_io_map直接按event_signal_map使用。

       这里涉及到了一个宏定义HT_HEAD以及一个结构体event_map_entry,先来看看event_map_entry结构体的定义。

#ifdef EVMAP_USE_HT
struct event_map_entry {
	HT_ENTRY(event_map_entry) map_node;  //构成哈希表中同一个桶下的链表
	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;    //存放同一个文件描述符/信号值下的所有event
	} ent;
};

       可以看到,在event_map_entry这一结构体中,还涉及到了一个HT_ENTRY宏定义,除此之外,其中包含了一个文件描述符/信号值成员fd,以及一个联合体中定义了一个evmap_io类型的结构体。对于这里的HT_ENTRY以及前面的HT_HEAD,各自的宏定义如下所示:

#define HT_HEAD(name, type)                                             \
  struct name {                                                         \
    /* The hash table itself. */                                        \
    struct type **hth_table;  /* 哈希表 可以当做数组 hth_table[i]就是该数组中第i个struct type * 类型元素*/                                          \
    /* 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; /*哈希表长度在素数数组中的索引*/                                  \
  }

#ifdef HT_CACHE_HASH_VALUES
#define HT_ENTRY(type)                          \
  struct {                                      \
    struct type *hte_next;                      \
    unsigned hte_hash;                          \
  }
#else
#define HT_ENTRY(type)                          \
  struct {                                      \
    struct type *hte_next;                      \
  }
#endif

       对于HT_HEAD宏定义,其中包含5个成员,各自的含义可参照注释。需要注意的是第一个成员struct type **hth_table,从成员名来看就是哈希表,这是一个二级指针,在C语言中规定,p[i]等价于*(p+i),换到这里来看,hth_table是一个指向type *类型的指针,因此(p+i)就是从首地址偏移了i个type *类型大小的地址,而*(p+i)自然也就是从首地址算起,第i个type *类型的元素了,因此,hth_table作为一个二级指针,那么hth_table[i]也就是第i个type*类型的元素,hth_table也就相当于一个type *类型的数组了。

       再来看 HT_ENTRY这一宏定义,它定义了一个匿名结构体,该结构体的接个头由宏定义HT_CACHE_HASH_VALUES控制,在libevent中,我目前尚未找到HT_CACHE_HASH_VALUES的定义,也就是说,该宏定义默认为未定义的,可由用户自行选择是否定义。

       接下来,替换以上两个宏定义,再回到event_map_entry和event_io_map中,得到各自定义如下:

struct event_map_entry
{
    struct
    {
        struct event_map_entry *hte_next;   //构成链表
#ifdef HT_CACHE_HASH_VALUES
        unsigned hte_hash;
#endif
    }map_node;
 
    evutil_socket_t fd;
    union
    {
        struct evmap_io evmap_io;
    }ent;
};


struct event_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_io_map中,二级指针hth_table刚刚说了,可以看做是一个数组,这个数组的各个元素类型为event_map_entry *类型,而在event_map_entry结构体的定义中,一个hte_next很明显表明了这里会存在一个event_map_entry的链表。在来看后面的evmap_io结构体定义,如下所示:

struct evmap_io
{
    struct event_list events;   //event双向链表
    ev_uint16_t nread;    //链表中读事件的个数
    ev_uint16_t nwrite;   //链表中写事件的个数
};

        关于event_list类型,可以在event_struct.h中找到其相关语句:(关于TAILQ

TAILQ_HEAD (event_list, event);  //名为event_list的头结点,链表结点为event类型

        也就是说,在evmap_io中,定义了一个event的双向链表,以及一个nread变量和nwrite变量,而一个event_map_entry对应一个evmap_io,也就对应一个event的双向链表。

        因此总结得出event_io_map的结构,如下所示:

       这样,我们就大概知道了libevent中哈希表的结构:

       hth_table就是哈希表中的“桶数组”,它的每一个元素都是一个“桶”,这里将哈希表分为主链和冲突链,哈希表主链上的各个结点放置的是地址连续的event_map_entry指针,通过下标如hth_table[i]就可以直接访问第i个结点,它的底层就是*(hth_table+i),这是一个寻址的过程,时间复杂度为O(1)。主链上每一个这样的event_map_entry指针都指向一个event_map_entry实体。如果有多个event_map_entry实体对应同一个桶,这就引起了哈希冲突,从图中也可得知,libevent是采用拉链法来解决哈希冲突的,如图中褐色框中的内容所示。

       每个event_map_entry都对应了一个fd,对于event这是一个文件描述符,对于signal这是一个信号值,也就对应了一个监听事件。如果多个事件对应同一个fd,那么这些事件就会被放到evmap_io所对应的event事件双向链表中,evmap_io中的nread和nwrite则分别记录了这个双向链表中读类型和写类型的event数量

       总之,event_io_map中会保存所有通过event_add添加的io event。

哈希表操作函数

      在ht-internal.h中,哈希表相关的函数主要都是以宏函数来实现的,找到这些函数的宏定义如下所示:

#define HT_EMPTY(head)                          \
  ((head)->hth_n_entries == 0)

/* How many elements in 'head'? */
#define HT_SIZE(head)                           \
  ((head)->hth_n_entries)

#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_RE
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值