ngx_hash分析(四)

一、哈希表概述

 

Hash,一般翻译做“散列”,也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。

    哈希表由多组key-value对组成,建立哈希表是为了提高查找的效率,避免在每次查找时要进行大量的关键字比较。在构造哈希望表时要考虑使用何种办法确定哈希表的key的散列值和使用何种办法解决不同的key产生相同的散列值。

 

二、nginx哈希表概述

   nginx哈希表只支持一次构建,构建后不能再往里面添加元素。在构建时会根据一些事先设定的参数计算出哈希表的大小。nginx使用了bucket(桶)的概念,一个哈希表可包含多个桶,每个桶可包含多个元素,同一个桶中所有元素产生的散列值是相同的,采用一维数组存储同散列值的key。而nginx中哈希值采用key经过哈希计算后得到一个32位无符号整数,然后使用除留余数法确定。如key为"张三明",经过哈希后得到50,若哈希望表大小为N,则散列值为 50 % N。

 

 

三、nginx哈希表结构

 

  nginx采用三级管理结构,如图

 

 

     key:计算哈希表主键哈希值的函数指针。

     max_size:哈希表中桶的最大数量。

     bucket_size:每个桶中最大的容量(字节)。

     name:哈希表名称

 

 

     buckets:桶的指针,指向第一个桶bucket1。

     size:哈希表中实际有多少个桶。

 

     value:指针,指向哈希表key-value对应的value值。

     len:哈希表key主键字符串的长度。

     name:哈希表key字符串首地址

 

 

 

     每个桶占用空间都要对齐到ngx_cacheline_size上,所有桶占用空间都要对齐到ngx_cacheline_size上。

     每个桶结束是以NULL字符结束。如桶1中包含3个成员,则在第3个成员结束后把紧接着的四个字节置为NULL 。

 

四、源码分析

 

//作用:构建哈希表

//参数:hinit--哈希表的初始化信息;names--包含key-value对,nelts--names数量

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;

 

    //判断桶的最大容量是否能容纳ngx_hash_key_t
    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;
        }
    }

    //分配max_size个数组空间,用来存储每个桶的占用空间
    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 *);
    //初步算出哈希表中需要多少个桶
    start = nelts / (bucket_size / (2 * sizeof(void *)));
    start = start ? start : 1;

    if (hinit->max_size > 10000 && 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));
       //遍历所有ngx_hash_key_t的key字符串,判断当前桶的数量是否能存储所有key字符串。
        for (n = 0; n < nelts; n++) {
            if (names[n].key.data == NULL) {
                continue;
            }

            //计算出 names[n]应该存放在哪个桶中
            key = names[n].key_hash % size;

            //计算key桶占用的空间
            test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));

#if 0
            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
                          "%ui: %ui %ui /"%V/"",
                          size, key, test[key], &names[n].key);
#endif
            //若key桶的容量大于桶的最大容量,则跳转到next,增加哈希表中桶的数量
            if (test[key] > (u_short) bucket_size) {
                goto next;
            }
        }
        //找到合适的桶数量
        goto found;

    next:

        continue;
    }

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

    ngx_free(test);

    return NGX_ERROR;

found:
    //初台化每个桶的初始容量为4个字节
    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;
    //把每个桶的实际占用空间大小对齐到ngx_cacheline_size边界上,同时计算所有桶占用的空间len
    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没初始化,分配ngx_hash_wildcard_t空间和size个桶的ngx_hash_elt_t 管理成员。
    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;
        }
        //第一个桶的管理成员ngx_hash_elt_t
        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;
        }
    }

    //分配桶的实际占用空间,并对齐到ngx_cacheline_size边界上
    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;
    }
    //初始化每个桶的成员。桶的成员保存key字符串,value值地址,key字符串的长度。

    //同时计算每个桶所有成员占用的空间
     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]));
    }
    //在每个桶末成员后添加NULL结束字符,以标示每个桶的结束标志。
    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);
    //赋值桶的管理成员buckets开始地址
    hinit->hash->buckets = buckets;
    hinit->hash->size = size;

#if 0

    for (i = 0; i < size; i++) {
        ngx_str_t   val;
        ngx_uint_t  key;

        elt = buckets[i];

        if (elt == NULL) {
            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
                          "%ui: NULL", i);
            continue;
        }

        while (elt->value) {
            val.len = elt->len;
            val.data = &elt->name[0];

            key = hinit->key(val.data, val.len);

            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
                          "%ui: %p /"%V/" %ui", i, elt, &val, key);

            elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
                                                   sizeof(void *));
        }
    }

#endif

    return NGX_OK;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值