libevent中evmap實現(哈希表)

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;
}

輸出結果:

相關文章
相關標籤/搜索