这个ngx_hash分析费了好长时间,在这里写下熟悉的过程以供参考,首先是下面的理解
然后可以结合这个网址的文章http://kofreestyler.blog.163.com/blog/static/10779075120112895055512/
一、哈希表概述
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个字节