一、哈希表概述
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;
}