NGINX(三)HASH表

前言

nginx的hash表有幾種不一樣的種類, 不過都是以ngx_hash_t爲基礎的, ngx_hash_t是最普通的hash表, 衝突採用的是鏈地址法, 不過這裏衝突的元素不是一個鏈表, 而是一個數組, 爲了加快訪存速度,這種hash表只用於存儲一些靜態的信息, 例如全部頭部信息, 配置信息等等.nginx

涉及數據結構

/*hash元素數據結構包含key和value*/
typedef struct {
    /*hash值*/
    void             *value;
    /*hash表原始key的長度, 即name長度*/
    u_short           len;
    /*name即原始的key值*/
    u_char            name[1];
} ngx_hash_elt_t;

/*普通hash表*/
typedef struct {
    /*hash元素*/
    ngx_hash_elt_t  **buckets;
    /*hash表中元素數量*/
    ngx_uint_t        size;
} ngx_hash_t;

/*hash表中key數據結構, 主要用於傳遞一個hash時使用*/
typedef struct {
    /*原始key值*/
    ngx_str_t         key;
    /*hash函數計算過key值*/
    ngx_uint_t        key_hash;
    /*hash表中value值*/
    void             *value;
} ngx_hash_key_t;

typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);

/*普通hash表初始化函數*/
typedef struct {
    /*hash表指針*/
    ngx_hash_t       *hash;
    /*未使用*/
    ngx_hash_key_pt   key;
    
    /*hash表中容納最大元素數量*/
    ngx_uint_t        max_size;
    /*hash表中桶的大小, 即容納一個元素ngx_hash_elt_t大小*/
    ngx_uint_t        bucket_size;
    
    /*hash表名字*/
    char             *name;
    /*內存池用於固定不變的一些數據結構使用*/
    ngx_pool_t       *pool;
    /*臨時的內存池,因爲內存池中內存是在內存池銷燬時統一釋放,所以這裏對於臨時變量使用*/
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;

hash表初始化

初始化hash結構, nginx的hash將內存分爲一系列桶, 每一個桶內放置一個元素或者是全部衝突的元素, 下面是hash存儲的示意圖, hash表初始化時, 首先肯定每一個桶的大小, 其次肯定hash表長度, 最後把傳進函數中的數據, 按照規則放入hash表中.數組

hash表分佈示意圖

|-----一個桶大小bucket_size-----|-----一個桶大小bucket_size-----|-----一個桶大小bucket_size-----|-----一個桶大小bucket_size-----|........
|--------key_hash%size=0--------|--------key_hash%size=1--------|---------key_hash%size=2-------|--------key_hash%size=3--------|........
|------------------|--------|...|------------------|--------|...|------------------|--------|...|------------------|--------|...|........
0  ngx_hash_elt_t     len     | 1  ngx_hash_elt_t      len    | 2  ngx_hash_elt_t      len      3  ngx_hash_elt_t      len      4........
                              |                               |                    ........
                  |                               |                    ........
                  取餘爲0的衝突元素依次放入桶內        取餘爲1的衝突元素           ........
/*計算桶中元素ngx_hash_elt_t大小, ngx_hash_elt_t結構體長度是可變的, 最後一個成員name[1], C語言慣用的手法, 可讓結構體自己和保存的數據鏈接在一塊兒, 只是一個
 *佔位指針, 有時候咱們定義爲name[0], 所以長度也就是sizeof(value) + sizeof(u_short) + sizeof(name) + len, 可是咱們看到下面並非像咱們計算的這樣, 
 *因爲sizeof(u_short) + sizeof(name)值確定小於sizeof(void*), 結構體進行內存對齊時, 以成員最長長度進行對齊, 所以以sizeof(void*)進行對齊, 2表明的是sizeof(u_short). 
 */
#define NGX_HASH_ELT_SIZE(name)                                               \
    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
ngx_hash_elt_t分佈示意圖
|------------------------------|---------------|----------------------|
           sizeof(void*)        sizeof(u_short)     長度爲len的name(name只是一個佔位指針)
ngx_int_t
ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
{
    u_char          *elts;
    size_t           len;
    u_short         *test;
    ngx_uint_t       i, n, key, size, start, bucket_size;
    ngx_hash_elt_t  *elt, **buckets;

    for (n = 0; n < nelts; n++) {
        /*遍歷判斷是否有元素長度大於咱們事先預估的桶大小, 一個桶的大小須要可以容納全部的衝突元素*/
        if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
        {
            ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
                          "could not build the %s, you should "
                          "increase %s_bucket_size: %i",
                          hinit->name, hinit->name, hinit->bucket_size);
            return NGX_ERROR;
        }
    }

    test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
    if (test == NULL) {
        return NGX_ERROR;
    }
    
    /*上面比較時,加了一個指針長度, 這裏減去*/
    bucket_size = hinit->bucket_size - sizeof(void *);
    
    /*預估hash表大小, 這裏以ngx_hash_elt_t爲最小8字節進行計算*/
    start = nelts / (bucket_size / (2 * sizeof(void *)));
    start = start ? start : 1;

    if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
        start = hinit->max_size - 1000;
    }

    for (size = start; size <= hinit->max_size; size++) {

        ngx_memzero(test, size * sizeof(u_short));

        for (n = 0; n < nelts; n++) {
            if (names[n].key.data == NULL) {
                continue;
            }

            key = names[n].key_hash % size;
            /*累加衝突元素的長度*/
            test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));

            /*元素大小大於桶大小, 累加size值, 從新進行分配*/
            if (test[key] > (u_short) bucket_size) {
                goto next;
            }
        }

        goto found;

    next:

        continue;
    }

    ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0,
                  "could not build optimal %s, you should increase "
                  "either %s_max_size: %i or %s_bucket_size: %i; "
                  "ignoring %s_bucket_size",
                  hinit->name, hinit->name, hinit->max_size,
                  hinit->name, hinit->bucket_size, hinit->name);

found:

    /*重置記錄元素位置的臨時數組*/
    for (i = 0; i < size; i++) {
        test[i] = sizeof(void *);
    }

    for (n = 0; n < nelts; n++) {
        if (names[n].key.data == NULL) {
            continue;
        }

        key = names[n].key_hash % size;
        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
    }

    len = 0;

    /*計算全部元素總長度*/
    for (i = 0; i < size; i++) {
        if (test[i] == sizeof(void *)) {
            continue;
        }
        
        /*此處進行了字節對齊, 加快訪存速度*/
        test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

        len += test[i];
    }
    
    /*hash表爲size個桶分配內存,保存全部桶的指針*/
    if (hinit->hash == NULL) {
        hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                             + size * sizeof(ngx_hash_elt_t *));
        if (hinit->hash == NULL) {
            ngx_free(test);
            return NGX_ERROR;
        }

        buckets = (ngx_hash_elt_t **)
                      ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));

    } else {
        buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
        if (buckets == NULL) {
            ngx_free(test);
            return NGX_ERROR;
        }
    }
    
    /*整個hash表分配內存*/
    elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
    if (elts == NULL) {
        ngx_free(test);
        return NGX_ERROR;
    }

    elts = ngx_align_ptr(elts, ngx_cacheline_size);

    for (i = 0; i < size; i++) {
        if (test[i] == sizeof(void *)) {
            continue;
        }

        buckets[i] = (ngx_hash_elt_t *) elts;
        elts += test[i];

    }
    
    /*重置記錄元素位置臨時數組*/
    for (i = 0; i < size; i++) {
        test[i] = 0;
    }

    /*循環遍歷將全部元素, 填入hash表中*/
    for (n = 0; n < nelts; n++) {
        if (names[n].key.data == NULL) {
            continue;
        }

        key = names[n].key_hash % size;
        elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);

        elt->value = names[n].value;
        elt->len = (u_short) names[n].key.len;

        ngx_strlow(elt->name, names[n].key.data, names[n].key.len);

        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
    }
    
    /*hash表中空缺的位置置空*/
    for (i = 0; i < size; i++) {
        if (buckets[i] == NULL) {
            continue;
        }

        elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);

        elt->value = NULL;
    }

    ngx_free(test);

    hinit->hash->buckets = buckets;
    hinit->hash->size = size;

    return NGX_OK;
}

hash表查找

瞭解了hash表存儲結構, 從hash表中查找元素變得簡單多了, 首先取出元素對應的桶, 而後依次比較name是否相等, 相等則返回對應值value數據結構

void *
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;

    /*取出元素對應的桶位置*/
    elt = hash->buckets[key % hash->size];

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

    while (elt->value) {
        if (len != (size_t) elt->len) {
            goto next;
        }
        
        /*比較對應的name值*/
        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;
}
相關文章
相關標籤/搜索