nginx源码分析2———基础数据结构四(ngx_hash_t)

相关介绍

nginx哈希表的实现是 ngx_hash_t。ngx_hash_t的实现也与数据结构教科书上所描述的hash表的实现是大同小异。
对于常用的解决冲突的方法有线性探测二次探测开链法等。ngx_hash_t使用的是开链法

源码分析

哈希表的结构

ngx_hash_elt_t是hash表中每一个元素的结构,实际存储在hash表中的结构

typedef struct {
    void             *value;
    u_short           len;
    u_char            name[1];
} ngx_hash_elt_t;

ngx_hash_key_t这是用于初始化hash表的结构,并不会存储在hash表内部

typedef struct {
    ngx_str_t         key;
    ngx_uint_t        key_hash;
    void             *value;
} ngx_hash_key_t;

key表示元素对应的key
key_hash表示元素的key的hash值
value 表示对应元素的value

ngx_hash_t是实际的hash表的结构。二维数组buckets是hash表的桶,size是桶的数量。

typedef struct {
    ngx_hash_elt_t  **buckets;
    ngx_uint_t        size;
} ngx_hash_t;

ngx_hash_init_t结构体用于初始化函数初始化hash表

typedef struct {
    ngx_hash_t       *hash;
    ngx_hash_key_pt   key;
    ngx_uint_t        max_size;
    ngx_uint_t        bucket_size;
    char             *name;
    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;

hash 该字段如果为NULL,那么调用完初始化函数后,该字段指向新创建出来的hash表。如果该字段不为NULL,那么在初始的时候,所有的数据被插入了这个字段所指的hash表中
key指向从字符串生成hash值的hash函数
max_size是hash表中的桶的个数。该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询起来的速度更快。
bucket_size每个桶的最大限制大小,单位是字节。如果在初始化一个hash表的时候,发现某个桶里面无法存的下所有属于该桶的元素,则hash表初始化失败
name这是这个hash表的名字
pool是hash表分配内存使用的pool
temp_pool是hash表使用的临时pool,在初始化完成以后,该pool可以被释放和销毁掉。

哈希表的初始化
ngx_int_t 
ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
ngx_uint_t nelts);

hinit是上面所述的初始化hash表的结构

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++) {
    /*bucket_size不可以小于一个(内存对齐的ngx_hash_elt_t的大小加上一个指针的大小)*/
        if (hinit->bucket_size < 
/*NGX_HASH_ELT_SIZE用于获取(单个内存对齐的ngx_hash_elt_t)的大小*/
             NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
        {
        //如果桶的大小还不足以装下一个元素,就给出创建失败
            ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,"could not build the %s, you shouldincrease%s_bucket_size: %i", hinit->name, hinit->name, hinit->bucket_size);
            return NGX_ERROR;
        }
    }   
    //直接分配内存,大小为无符号short乘以hash表中桶的个数
    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表包含桶的数量的初始尝试大小,小于该大小的桶的数量一定初始化不了hash表*/
    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;
            }
            //hash散列
            key = names[n].key_hash % size;
            /*计算散列后,每一个桶的总大小(实际只是计算,并无任何处理)*/
            test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
            /*必须保证每一个桶都能装下所有的元素,不然总的桶的数量+1,然后进行下一次尝试*/
            if (test[key] > (u_short) bucket_size) {
                goto next;
            }
        }
        goto found;
    next:
        continue;
    }
    //如果实在没有找到,那么将给出警告,并且将包含桶的数量设置为最大
    size = hinit->max_size;
    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:
    //其实test里面都是存放的计算的需要的大小
    /*先初始化为一个指针所占的大小,多加一个指针,最后会发现
    该指针会放在一个桶的所有元素的后面,并赋值为空*/
    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;
    //把每一个桶需要的内存(内存对齐后)加起来,放到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没有分配,那么久在pool里面给它分配内存
    if (hinit->hash == NULL) {
        hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));
        //分配失败就free test然后直接返回
        if (hinit->hash == NULL) {
            ngx_free(test);
            return NGX_ERROR;
        }
        /*(指向ngx_hash_elt_t 的指针)的一个指针,相当于忽略前面的ngx_hash_wildcard_t,从size * sizeof(ngx_hash_elt_t *)开始指向指针的指针给buckets*/
        buckets = (ngx_hash_elt_t **)((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));
    } else {
    //如果已经给定了hash的内存
        buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
        //只需要分配桶的内存
        if (buckets == NULL) {
            ngx_free(test);
            return NGX_ERROR;
        }
    }
    //分配内存
    elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
    if (elts == NULL) {
        ngx_free(test);
        return NGX_ERROR;
    }
    //对elts进行内存对齐修正
    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];
    }

    //memset test
    for (i = 0; i < size; i++) {
        test[i] = 0;
    }

    //将每一个key都放进去
    for (n = 0; n < nelts; n++) {
        if (names[n].key.data == NULL) {
            continue;
        }
        key = names[n].key_hash % size;
        //buckets[key]表示该元素在第几个桶
        //test[key]表示该桶,这个元素的起始地址
        /*第一个起始地址是桶的开始地址buckets[key],第二个肯定是第一个基础上加起始地址,一次类推,上次的地址绝对偏差保存在test数组里面,每一个桶对应test里面的一个元素*/
        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]));
    }

    for (i = 0; i < size; i++) {
        if (buckets[i] == NULL) {
            continue;
        }
        //桶里面最后一个元素的后一个位置
        elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);
        追加一个NULL,分配的时候也多分配了一个指针。
        elt->value = NULL;
    }
    //释放test
    ngx_free(test);
    //返回数据
    hinit->hash->buckets = buckets;
    hinit->hash->size = size;
    return NGX_OK;
}
hash的查找

hash表的查找比较简单,和我们常见的一样,先hash取值,然后从查找。keyname对应的hash值

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

    //这个桶是NULL,那么久直接返回NULL。
    if (elt == NULL) {
        return NULL;
    }

    while (elt->value) {
        //如果key的长度不一样,肯定不同,直接next
        if (len != (size_t) elt->len) {
            goto next;
        }

        //比较key的大小,要是不一样,直接next
        for (i = 0; i < len; i++) {
            if (name[i] != elt->name[i]) {
                goto next;
            }
        }
        //如果一样,就直接return掉。
        return elt->value;
    next:
        //指针后移到下一个元素的位置,并进行内存对齐矫正
        elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,sizeof(void *));
        continue;
    }
    return NULL;
}

总结

hash表的实现结构比较简单,但是初始化的时候比较繁琐,会调节几个参数,然后再来根据初始化的key来计算每一个桶的大小,然后将所有的元素填充进去。hash表的查找比较简单,也比较传统,贴上来也是给各位压压惊,我们下一节再研究ngx_hash_wildcard_t(支持通配符的散列表)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值