• 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
    ", b, elm->key, elm->value);
    				elm = elm->map_node.hte_next;
    			}
    		}
    	}
    }
    
    
    int main()
    {
    	test();
    	return 0;
    }
    

    输出结果:

  • 相关阅读:
    精选文章
    Eclipse Git插件切换分支的时候不要Reset
    Spring ContentNegotiatingViewResolver
    Spring3 MVC 类型转换
    FTP DOS 命令行
    Java xml 解析
    Java 实现FTP上传和下载
    Hibernate update 和 merge 、saveOrUpdate的区别
    Spring MVC 文件下载时候 发现IE不支持
    Javasript 正则匹配任意字符
  • 原文地址:https://www.cnblogs.com/yangang92/p/5857096.html
Copyright © 2020-2023  润新知