nginx中hash表的設計

hash表的整體設計要素

先回顧一下hash表的設計的幾個要素:

  • hash函數的構造-散列函數
  • 衝突處理方式
  • 裝填因子大小的選擇。裝填因子 a=n / m。其中m爲hash表的bucket個數;(n爲關鍵字的個數。裝填因子越大,產生hash衝突就嚴重。)

hash函數構造方法

經典的字符串hash構造算法主要以下幾個:

  • BKDRHash
  • APHash
  • DJBHash
  • JSHash
  • RSHash
  • SDBMHash
  • PJWHash
  • ELFHash

nginx中默認的hash算法採用的是BKDR hash算法

/* BKDR算法推導公式, 將任意長度的字符轉換成數字 */
#define ngx_hash(key, c)   ((ngx_uint_t) key * 31 + c)

/* 字符轉換成小寫,字母的大小寫相差32 */
#define ngx_tolower(c)      (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)


/* 上次hash的結果作爲當前key值,其目的是爲了擴大key值衝突的間隔,減少衝突 */
ngx_uint_t
ngx_hash_key(u_char *data, size_t len)
{
    ngx_uint_t  i, key;

    key = 0;

    for (i = 0; i < len; i++) {
        key = ngx_hash(key, data[i]);  /* key * 31 + c   test*/
    }

    return key;
}

/* 將字符串小寫後,再使用BKDR算法將任意長度字符串映射成整型*/
ngx_uint_t
ngx_hash_key_lc(u_char *data, size_t len)
{
    ngx_uint_t  i, key;

    key = 0;

    for (i = 0; i < len; i++) {
        key = ngx_hash(key, ngx_tolower(data[i]));
    }

    return key;
}

BDKhash算法推理參考:https://blog.csdn.net/wanglx_/article/details/40400693

常見的衝突處理方法

  • 開放定(尋)址法:衝突產生時候,可存放新項的空閒地址可以被同義詞和非同義詞都使用。(hash值相同的關鍵詞稱爲同義詞)
    • 線性探測法:順序查看錶中下一個單元直到產生一個空閒單元。
      • 容易導致大量元素堆積,降低查找效率。
    • 平方探測法:計算hash值地址增量進行
      • 可以避免堆積問題,缺點是不能探測到散列表上所有單元,但至少能探測到一半單元
    • 再離散法:使用兩個hash函數,第一個發生衝突時候,用第二個hash函數計算地址增量
  • 拉鍊法: 一般拉鍊法用的比較多,主要插入刪除比較方便。
    • 簡單的講就是將hash值一樣的同義詞用鏈表連接起來
    • 優點:插入和刪除比較方便
    • 缺點是:指針需要額外的空間,故當結點規模較小時,開放定址法較爲節省空間,而若將節省的指針空間用來擴大散列表的規模,可使裝填因子變小,這又減少了開放定址法中的衝突,從而提高平均查找速度

nginx當中使用的是連續非空槽存儲碰撞元素的方法—>線性探測法:衝突發生時候,順序查看錶的下一個單元找到一個空閒的表單元

ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
    ngx_uint_t       i;
    ngx_hash_elt_t  *elt;
    /* 先獲取對應key的hash值位置 */
    elt = hash->buckets[key % hash->size];

    if (elt == NULL) {
        return NULL;
    }

    while (elt->value) {
        if (len != (size_t) elt->len) {
            goto next;
        }

        for (i = 0; i < len; i++) {
            if (name[i] != elt->name[i]) {
                goto next;
            }
        }

        return elt->value;

    next:
        /* 發現衝突後,從當前位置依次往後面找空閒的表單元存放 */
        elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
                                               sizeof(void *));
        continue;
    }

    return NULL;
}

nginx中hash表的應用場景

nginx實現了兩類hash結構,

  • 一類是key中包含通配符的ngx_hash_wildcard_t,這裏的通配符指*號。(可以利用正則表達式匹配,主要用來虛擬主機的匹配)
  • 另一類則是key中不包含通配符的ngx_hash_t

以上分析的是nginx中不含通配符的hash表結構,nginx當中hash表的大小是靜態固定的,如果數據項大的話,可以改成可變大小的hash表結構

含有帶通配符的hash表設計可以參考:https://www.cnblogs.com/chengxuyuancc/p/3782808.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章