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