本文讲述: nginx 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; /* nginx hash结构大致是这样: hash结构中有N个桶, 每个桶存放N个元素(即<k,v>),在内存中, 用一个指针数组记录N个桶的地址,每个桶又是一个 ngx_hash_elt_t 数组 指针数组 和 ngx_hash_elt_t 数组 在一个连续的内存中. 优点: 使用数组提高寻址速度 缺点: hash表初始化后,只能查询,不能修改. 当然hash结构本来就是数组形式,但对于冲突的元素大多是用链表形式存放,再挂载到hash数组上. nginx的hash结构过程: 首先 每个桶的空间大小固定 通过 ngx_hash_init_t.bucket_size 指定; 然后 根据元素的个数和桶的固定大小计算出需要多少个桶. 然后 计算哪些元素存放到哪个桶中,方法就是 (元素的hash值 % 桶的个数) 这时 需要多少个桶,这些桶需要多少内存空间,每个桶存放多少元素,需要多少内存空间就知道, 申请所有桶的内存空间,即为 ngx_hash_init_t.hash.buckets 指针数组. 申请每个桶存放元素的存储空间 = 该桶元素占用的内存空间 + void指针 为了提高查询效率,申请一个连续内存空间存放 所有桶的元素. 然后把这片连续的内存空间映射到 ngx_hash_init_t.hash.buckets 指针数组. 然后为每个桶的元素赋值. 最后将每个桶的"结束元素"置为NULL void指针的用途: 为桶的结束标记, 在 ngx_hash_find() 遍历桶是判断 etl->value 是否为NULL时用到. 你可能有以为 "结束元素"只有一个void*指针的空间转换成 ngx_hash_elt_t 后会不会越界操作? 答案是不会的, void*指针的空间转换成 ngx_hash_elt_t 后只操作 ngx_hash_elt_t.value , 而 ngx_hash_elt_t.value 刚好只占 void指针空间大小. 指定桶的大小的好处: 保证每个桶存放元素的个数不超过一定值,目的是为了提高查询效率. */ /* 每个桶至少能存放一个元素 + 一个void指针, 指针的目的是为了 */ 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; } } /* 用于记录每个桶的临时大小 */ 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 *); /* 计算需要桶数目的下界 每个元素最少需要 NGX_HASH_ELT_SIZE(&name[n]) > (2*sizeof(void*)) 的空间 因此 bucket_size 大小的桶最多能容下 bucket_size/(2*sizeof(void*)) 个元素 因此 nelts 个元素就最少需要start个桶。 */ start = nelts / (bucket_size / (2 * sizeof(void *))); start = start ? start : 1; if (hinit->max_size > 10000 && hinit->max_size / nelts < 99) { start = hinit->max_size - 1000; } /* 从最小桶数目开始试,计算容下 nelts 个元素需要多少个桶 */ 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) { /* 说明 size 个桶,容不下 nelts 个元素 */ 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: /* 执行到这里就得到了 容下 nelts 个元素需要 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; /* 计算第N个元素放置到哪个桶中 */ test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); /* 这个桶的空间扩大到 容下第 n 个元素 */ } len = 0; /* 将每个桶的实际大小对应到 cacheline 并计算数所用桶的总大小 */ 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为NULL, 就分配 ngx_hash_wildcard_t 结构, 照 ngx_hash_init_t 的结构来说 应该分配 ngx_hash_t. 这里分配 ngx_hash_wildcard_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; } /* 赋值 桶数组的起始位置 */ 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; } /* 将 names 中的元素 赋值给 对应桶中的 ngx_hash_elt_t 数组中的元素, names的元素在 ngx_hash_elt_t 数组的位置是无序的. */ 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); 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; }