Nginx-ngx_hash
来源:互联网 发布:删失数据的定义 编辑:程序博客网 时间:2024/05/21 10:21
ngx_hash是nginx内部封装的一个hash实现,其实现思想和我们平时讨论的实现差别不大,但是有几个地方还是值得我们仔细研究下。
1, ngx_hash实现的是一个静态的hash表,只能查询不能插入修改,这个应该是和应用场景相关。
2, hash桶的数量并不是事先指定好,而是在初始初始化hash表的时候通过一定的技巧来找到具体所需要的桶的数量。
3, 解决冲突的办法就是开链,每个桶都是一个链表,但是在实现的时候这里还是有很多技巧。
4, 最应该值得一提的是:注意这里hash实现的时候对内存的高效利用。
那么先来看看涉及到的数据结构:
typedef struct { void *value; //hash key对应的value值的内存地址 u_short len; //hash key string 对应的长度 sizeof(u_short) = 2 /*key 对应内存地址,这里为什么只申请大小为1个元素的字节?实际上每个elt所需要的空间会按照需求来分配,这里利用了ngx_pool里面的技巧,多分配的空间也会自动最佳在name[1]后面,这么做的好处就是做到了空间的高效利用 */ u_char name[1]; } ngx_hash_elt_t;typedef struct { ngx_hash_elt_t **buckets; //hash表 ngx_uint_t size; //桶的个数} ngx_hash_t;/*这个结构就是用来初始化一个hash表用的*/typedef struct { ngx_str_t key; //hash key的原始值 ngx_uint_t key_hash; //key经过hash函数变换后得到的hash key void *value; //key对应的value} ngx_hash_key_t;typedef struct { ngx_hash_t *hash; //hash表的指针 ngx_hash_key_pt key; //hash函数 ngx_uint_t max_size; //桶的最大数量 ngx_uint_t bucket_size; //桶的大小 char *name; //hash表明 ngx_pool_t *pool; //hash中的内存来源 ngx_pool_t *temp_pool; //创建hash表过程中的临时表,创建完了可以删除掉} ngx_hash_init_t;
结合ngx_hash_init_t的数据结构,画出起内存空间的逻辑结构如下:
在看hash初始化过程之前需要介绍下怎么动态的计算每个元素的大小,先看一个宏定义:
#define NGX_HASH_ELT_SIZE(name) \ (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))在计算元素占用空间的时候分为三部分,需要结合ngx_hash_elt_t这个结构体来看:
typedef struct { void *value; u_short len; u_char name[1];} ngx_hash_elt_t;1,sizeof(void *) : 对应的是 *value这个指针所占空间大小 从这里可以看出,实际上value的值是没有拷贝进hash表的,只是用了一个指针来索引。
2, (name)->key.len : 这里对应到原始的key的长度,实际上这个长度就是name[1]开始往后的内存空间。为什么key的原始串被拷贝进了hash表中,但是value不拷贝了?个人猜测是由于key的长度不会太大,但是value可能是一个非常长的字符串,所以权衡下还是不拷贝了。
3, 2: 这个固定值实际上就是sizeof(u_short) 的值,也就是第二个变量的空间大小
最后再这个元素所在的实际空间上做了一个地址对齐操作。
接下来看看hash表的初始化过程,说实话这个过程还是有点复杂,需要认真仔细体会,并且要非常熟悉指针的相关操作。
ngx_int_tngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts){ /*此函数里有一些magic number,没有办法解释,只能说是作者的经验值*/ 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; //判断桶的大小是否够一个元素,因为每个元素的大小都不一样,所以需要逐个比较 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; } // 这里的sizeof(void *)就是一个magic number bucket_size = hinit->bucket_size - sizeof(void *); /*start的含义是: 在构造hash数组前需要探测需要多少个桶才够,那么start对应的 就是需要桶的个数,下面会先用一些方法探测出来实际需要的桶数*/ //元素较少的情况,那么起始探测值就会比较小 start = nelts / (bucket_size / (2 * sizeof(void *))); start = start ? start : 1; //元素较多,那么探测的起始值就会比较大 if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) { start = hinit->max_size - 1000; } //从小到大逐个探测,一旦探测到需要的桶的数量了,那么就停止 for (size = start; size < hinit->max_size; size++) { //测试数组,全赋值为0//test数组的含义是,test中每个元素对应到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; } } //一轮计算下来发现当前size个桶刚好满足需求了,那么就不探测了 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); //临时空间及时free掉 ngx_free(test); return NGX_ERROR;found: //当前size的值就是桶的个数 for (i = 0; i < size; i++) { //为每个桶多增加一个void*的指针空间,这个 //将作为每个桶的结束标记符,参见ngx_hash_elt_t的第一个成员 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; } //将每个桶的空间大小按照cacheline的大小对齐 test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size)); //计算所有桶中的元素需要的空间 len += test[i]; } if (hinit->hash == NULL) {//为所有桶分配其自身占用的空间, 这里为hash空间多申请了一个 sizeof(ngx_hash_wildcard_t)的空间 //是为了hash和wildcard hash共用一个hash结构 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; } //test[i]从这里开始的含义是: 到当前为止i号桶已经占用掉的空间 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]); //hash的val初始化 elt->value = names[n].value;//key的长度 elt->len = (u_short) names[n].key.len; //将key的原始值转换成小写后拷贝到name ngx_strlow(elt->name, names[n].key.data, names[n].key.len);//累计当前桶占用的空间大小 test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); } //为每个桶中的链表添加一个结束标记 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;}
理解了hash表的初始化过程,那么再看find函数的时候就相对轻松了
/*参数说明hash: 查询用的hash表。key: 经过hash函数处理过的键值name: key的原始字符串len : key的原始字符串长度*/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 //直接对key取模,就得到了对应的桶 elt = hash->buckets[key % hash->size]; if (elt == NULL) { return NULL; } //对于桶中每个元素逐个遍历 while (elt->value) { //key的长度都不相同必然不是要查找的key if (len != (size_t) elt->len) { goto next; } //逐个字符的比较原始key,这里需要注意的是name这个字符串必须保证传进来的都是小写字母 for (i = 0; i < len; i++) { if (name[i] != elt->name[i]) { goto next; } } //查找到了,那么直接返回value对应的地址指针 return elt->value; next: //注意这里的技巧,实际上bucket里不是一个链表,而是相邻两个元素紧密相连,直接取地址就行了 elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len, sizeof(void *)); continue; } return NULL;}在ngx_hash中还实现了一个可以使用通配符的hash表,由于实现比较复杂,其中参数需要结合上下文来理解,所以放到后面用到的时候再会头看看。
- Nginx-ngx_hash
- ngx_hash
- nginx 源码学习笔记(十)——基本容器——ngx_hash
- nginx 源码学习笔记(十)——基本容器——ngx_hash
- Nginx源码分析 - 基础数据结构篇 - hash表结构 ngx_hash.c
- nginx 源码学习笔记(十)——基本容器——ngx_hash
- ngx_hash分析(四)
- ngx_hash散列表
- nginx
- Nginx
- Nginx
- Nginx
- Nginx
- nginx
- nginx
- nginx
- nginx
- nginx
- Android 仿PhotoShop调色板应用(三) 主体界面绘制
- UVA 10341 Solve It 解方程 二分查找+精度
- Android 仿PhotoShop调色板应用(四) 不同区域颜色选择的颜色生成响应
- 网络编程技术(技术总结)
- Linux中权限(r、w、x)对于目录与文件的意义
- Nginx-ngx_hash
- 随想而已
- 一个简单gtk程序
- uva10344
- xfire接收自定义对象
- Android程序对不同手机屏幕分辨率自适应的方法
- 再谈volatile和sig_atomic_t
- 多线程--基础篇4--其他
- shell脚本学习笔记