Sword 哈希表

哈希表
哈希表是一种典型的以空间换取时间的数据结构,在没有冲突的情况下,对任意元素的插入、索引、删除的时间复杂度都是O(1)。
这样优秀的时间复杂度是通过将元素的key值以hash方法f映射到哈希表中的某一个位置来访问记录来实现的,即键值为key的元素
必定存储在哈希表中的f(key)的位置。当然,不同的元素的hash值可能相同,这就是hash冲突,有两种解决方法(分离链表发和开
放地址发),ngx采用的是开放地址法. 分离链表法是通过将冲突的元素链接在一个哈希表外的一个链表中,这样,找到hash表中的位置后,就可以通过遍历这个单链表来找到这个元素 开放地址法是插入的时候发现自己的位置f(key)已经被占了,就向后遍历,查看f(key)
+1的位置是否被占用,如果没被占用,就占用它,
否则继续相后,查询的时候,同样也如果f(key)不是需要的值,也依次向后遍历,一直找到需要的元素。
哈希表的本质
普通哈希表的查找比较简单,思想就是先根据hash值找到对应桶,然后遍历这个桶的每一个元素,逐字匹配是否关键字完全相同,
完全相同则找到,否则继续,直至找到这个桶的结尾(value
= NULL)。
nginx的hash表是固定元素长度的,就是一开始已知所有的键值对。无法动态添加,但是可以修改值

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#ifndef _NGX_HASH_H_INCLUDED_
#define _NGX_HASH_H_INCLUDED_


#include <ngx_config.h>
#include <ngx_core.h>

// 桶元素数据结构
typedef struct {
    void             *value;         // 存放键值
    u_short           len;           // 存放键长度
    u_char            name[1];       // 存放键名,这里是零长度数组的用法
} ngx_hash_elt_t;


typedef struct {
    ngx_hash_elt_t  **buckets;      // 桶列表
    ngx_uint_t        size;         // 桶个数
} ngx_hash_t;                       // 哈希表结构


// 键数据结构
typedef struct {
    ngx_str_t         key;           // 存放键名
    ngx_uint_t        key_hash;      // 存放键哈希值
    void             *value;         // 存放键值
} ngx_hash_key_t;


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


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;

void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);

ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
    ngx_uint_t nelts);

#define ngx_hash(key, c)   ((ngx_uint_t) key * 31 + c)
ngx_uint_t ngx_hash_key(u_char *data, size_t len);
ngx_uint_t ngx_hash_key_lc(u_char *data, size_t len);
ngx_uint_t ngx_hash_strlow(u_char *dst, u_char *src, size_t n);


#endif /* _NGX_HASH_H_INCLUDED_ */
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>


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;

#if 0
    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif
    
    // 通过哈希值找到对应的桶
    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->name[0] + elt->len表示当前元素的末尾
        elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
                                               sizeof(void *));
        continue;
    }

    return NULL;
}



/*
设计说明:
    这里是计算一个哈希表中桶中每个元素的大小
    桶的元素格式如下
    +---------------+---------------------+--------------------+
    |  键值(void*)  |  键名长度(u_short)  |   键名(u_char [])  |
    +---------------+---------------------+--------------------+
    
    因此单个桶元素的大小就是 键值+键名长度+键名
    
    这里为了提升性能,使用了内存对齐 ngx_align
*/
#define NGX_HASH_ELT_SIZE(name)                                               \
    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))

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;

    // 桶的个数为0,这是不行的
    if (hinit->max_size == 0) {
        ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
                      "could not build %s, you should "
                      "increase %s_max_size: %i",
                      hinit->name, hinit->name, hinit->max_size);
        return NGX_ERROR;
    }

    // 单个桶的大小至少得能放置一个元素
    for (n = 0; n < nelts; n++) {
        // 遍历所有元素,保证桶的大小可以放进去每一个元素
        
        /*
        设计说明:
            遍历所有元素,保证桶的大小可以放进去任何一个元素都是可以的
            
            每个桶是以NULL结尾,因此是 NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)
        */
        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 %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;
    }

    // 桶真实可以使用空间,需要将结束符NULL刨除掉
    bucket_size = hinit->bucket_size - sizeof(void *);
    
    /*
    设计说明:
        桶内元素的最小值就是 NGX_HASH_ELT_SIZE(),即2 * sizeof(void *)
        (bucket_size / (2 * sizeof(void *))); 可以计算出每个桶至多可以存放的元素数量
        nelts 是总元素数量
        nelts / (bucket_size / (2 * sizeof(void *))) 即可以计算出桶个数的最小值
        (bucket_size / (2 * sizeof(void *))); 这种算法是假设每个桶都塞满元素,现实中哈希表不可能每个桶都塞满元素
           因此start已经算是桶个数的最小值了,真实值肯定比start大
    */
    start = nelts / (bucket_size / (2 * sizeof(void *)));
    start = start ? start : 1;

    if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
        // 当目前桶的数量足够大,那么start的起始节点就可以更大一些,减少不必要的遍历,算是一种用内存空间换性能的算法
        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]));

#if 0
            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
                          "%ui: %ui %ui \"%V\"",
                          size, key, test[key], &names[n].key);
#endif

            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:

    // 此刻size就是最佳桶的个数
    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;
        }
        // 每个桶都要ngx_cacheline_size内存对齐,看出来nginx对于内存操作多么仔细
        test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

        len += test[i];
    }

    // 分配桶的内存
    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;
        }
    }

    // 分配桶内元素内存块
    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;
    }

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

        /*
        设计说明:
            每个桶最后一个元素是NULL
             这里有个特别的用法,单个桶的最后一个元素的大小是 sizeof(void*),实际上就是一个指针大小,虽然这里将这个内存空间强转成ngx_hash_elt_t*
             实际上真正的内存空间却没有sizeof(ngx_hash_elt_t),只有value一个属性的大小,这里体现了nginx对内存细致入微的操作
        */
        elt->value = NULL;
    }

    // 销毁临时内存
    ngx_free(test);

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


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

    return key;
}


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


ngx_uint_t
ngx_hash_strlow(u_char *dst, u_char *src, size_t n)
{
    ngx_uint_t  key;

    key = 0;

    while (n--) {
        *dst = ngx_tolower(*src);
        key = ngx_hash(key, *dst);
        dst++;
        src++;
    }

    return key;
}

 

posted on 2019-11-19 19:49  寒魔影  阅读(224)  评论(0编辑  收藏  举报

导航