nginx源码分析2———基础数据结构五(ngx_hash_wildcard_t)
2015-08-12 23:58
639 查看
相关介绍
所谓支持通配符的散列表,就是把基本的散列表(ngx_hash_t)中的元素的关键字,用去除通配符以后的字符作为关键字加入,原理其实没有那么神秘。当然得先熟悉ngx_hash_t,可以看上一节,对ngx_hash_t做深入的了解。ngx_hash_wildcard_t类型的hash表包含通配符在前的key或者是通配符在后的key。例如,对于关键字为”www.test.*”这样带通配符的情况,直接建立一个专用的后置通配符散列表,存储元素的关键字为”www.test”
源码分析
通配符hash表的结构
这事通配符hash表的结构,可以看到里面包含一个基本hash表。typedef struct { ngx_hash_t hash; void *value; } ngx_hash_wildcard_t;
下面ngx_hash_key_t是我们上一节说过,存储key,value,hashcode的结构体,用于初始化hash表
typedef struct { ngx_str_t key; ngx_uint_t key_hash; void *value; } ngx_hash_key_t;
通配符hash表的初始化
ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,ngx_uint_t nelts) { size_t len, dot_len; ngx_uint_t i, n, dot; ngx_array_t curr_names, next_names; ngx_hash_key_t *name, *next_name; ngx_hash_init_t h; ngx_hash_wildcard_t *wdc; /*在temp_pool上初始化数组curr_names,用于存放ngx_hash_key_t,容量为参数nelts */ if (ngx_array_init(&curr_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } //同上,初始化数组(如果不能理解,请看数组那一节) if (ngx_array_init(&next_names, hinit->temp_pool, nelts,sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } //遍历注意最后不是n++ for (n = 0; n < nelts; n = i) { //====================push name'.' 前面进入cur=============== dot = 0; //遍历每一个k的每一个字符 for (len = 0; len < names .key.len; len++){ //如果这个key包含一个dot就break该层for循环 if (names .key.data[len] == '.') { dot = 1; break; } } //push进数组一个元素,返回值是该元素的首地址 name = ngx_array_push(&curr_names); if (name == NULL) { return NGX_ERROR; } /*初始化该元素(注意该元素是ngx_hash_key_t),是用于初始化哈希表的结构*/ //初始化key name->key.len = len; name->key.data = names .key.data; //初始化hashcode name->key_hash = hinit->key(name->key.data, name->key.len); //初始化value name->value = names .value; /* 如果包含'.' 那么len为'.'所在的pos 如果不包含'.' 那么len为字符的长度。 例如test与test.t 他们的dotlen是一样的 */ dot_len = len + 1; if (dot) { /* 如果包含'.' 那么len包含'.'及'.'以前的长度 如果不包含'.' 那么len为字符的长度。 例如test与test.t分别计算的是 test与test.的长度 分别是4和5 */ len++; } //相当于将数组reset(给你一个眼神,自己体会) next_names.nelts = 0; //====================push name'.' 后面进入next============== //理解后的意思是key中含有点,而且不是在最后 if (names .key.len != len) { //在next_name压入元素 next_name = ngx_array_push(&next_names); if (next_name == NULL) { return NGX_ERROR; } //对该元素进行赋值 next_name->key.len = names .key.len - len; /*将该字符的起始地址移动'.'后 例如test.com相当与取com放到该数组 */ next_name->key.data = names .key.data + len; //设定hashcode为0 next_name->key_hash = 0; //value赋值 next_name->value = names .value; } //=================push name '.' 后面进入next============== //遍历names 后的所有参数names"数组"的元素 for (i = n + 1; i < nelts; i++) { /* e.g: tetetet与tetetetksj将相等 test.com与test.cn将相等 test与test.将相等(len = 4) test.与test将不相等(len = 5) */ if (ngx_strncmp(names .key.data, names[i].key.data, len) != 0) { break; } /*如果names .key没有包含'.',而且后面的key长度比这个key大,而且后面的key的第len+1个字符不是'.'*/ // e.g: tetetet与tetetetksj将成立 // e.g: tetetet与tetetet.ksj将不成立 //包含点一定不成立 if (!dot && names[i].key.len > len && names[i].key.data[len] != '.') { break; } next_name = ngx_array_push(&next_names); if (next_name == NULL) { return NGX_ERROR; } /* 如果主key为test.com test.com --> com test.cn --> cn */ next_name->key.len = names[i].key.len - dot_len; next_name->key.data = names[i].key.data + dot_len; next_name->key_hash = 0; next_name->value = names[i].value; } //===================push end============================ if (next_names.nelts) { h = *hinit; h.hash = NULL; //继续创建通配符hash表 /* 如果主key为test.com test.com --> com test.cn --> cn 新的key将是com cn等等字符串。 */ if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts, next_names.nelts) != NGX_OK) { return NGX_ERROR; } //该hash表的首部是ngx_hash_wildcard_t wdc = (ngx_hash_wildcard_t *) h.hash; /* 该字符串是刚刚好'.'前面的部分,如果是,就将该value存在通配符的value上 */ if (names .key.len == len) { wdc->value = names .value; } /*设置name后有没有点的标志(也就是name )最后一个是不是有点的,帮我们分清 1,test test.c 2,test. test.c 的区别 */ name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2)); } else if (dot) { //同上(由于内存对齐了,低位都是0) name->value = (void *) ((uintptr_t) name->value | 1); } } //初始化最初的hash表 使用cur数组 if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts, curr_names.nelts) != NGX_OK) { return NGX_ERROR; } return NGX_OK; }
这个是很难理解的,有一点要注意的是所有的key都不包含字符’.’号,点好都是设置标志的。就是names数组中元素的value值低两位bit必须为0,当然也肯定为0,因为内存对齐后,低两位为0,作者为了节约内存,使用了这点内存。如果要完全理解这段代码,应该边画边想,那样会更清晰。
本人不会绘图,现在就传入下面数据绘出一个基本的草图,不喜勿喷。
1. key = test;value = v1 2. key = test.;value = v2 3. key = test.a;value = v3 4. key = test.ab;value = v4 5. key = test.abc;value = v5 6. key = abc; value = v6 下面头部的hash可能还对应value结构(ngx_hash_wildcard_t)
通配符hash表的查找
有了上面的数据结构,让我们从ngx_hash_find_wc_head入手去查看一下通配符hash表如何查找。我习惯用key代表里面的name,习惯性的说key-value结构。对于name的初始化
*.test.com -------> com.test. *.te.com -------> com.te. *.st.com -------> com.st. .smjd.dff -------> dff.smjd .tst.dff -------> dff.tst
void * ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len) { void *value; ngx_uint_t i, n, key; n = len; //字符串尾部查找含有'.'的字符 while (n) { if (name[n - 1] == '.') { break; } n--; } //计算hash值 key = 0; for (i = n; i < len; i++) { key = ngx_hash(key, name[i]); } //在当前的hash中寻找hash,寻找范围为'.'后的 /* 如果name为test.com,那么这一层hash将寻找com */ value = ngx_hash_find(&hwc->hash, key, &name , len - n); //如果寻找存在 if (value) { //如果value里面存放的是ngx_hash_wildcard_t if ((uintptr_t) value & 2) { //如果这个name是在前面的,相当于test.com的test if (n == 0) { //如果查找到的这个key是包含'.'的 if ((uintptr_t) value & 1) { //返回NULL,交个上一层,让上一层返回自己的value return NULL; } //如果这个key不包含'.',那么这就是我们要的key //对内存标志进行恢复,并返回 hwc = (ngx_hash_wildcard_t *)((uintptr_t) value & (uintptr_t) ~3); return hwc->value; } //如果该key还有前面的部分比如test.gen的test //获取下一层通配符结构 hwc = (ngx_hash_wildcard_t *)((uintptr_t) value & (uintptr_t) ~3); //继续在下一层的结构查找test。 value = ngx_hash_find_wc_head(hwc, name, n - 1); //如果下一层返回不为NULL,就当成该key的value if (value) { return value; } /*否侧取这一层的value(如果这一层也为NULL,显而会继续往上层朔)*/ return hwc->value; } /*如果该key对应的value是最下层,也就是实际存放的就是value,且key后面一定是有'.'字符*/ if ((uintptr_t) value & 1) { //如果这个name(key)是在前面的,相当于test.com的test if (n == 0) { //返回并上朔 return NULL; } //否则返回该value,已经到最下层,查找完毕 return (void *) ((uintptr_t) value & (uintptr_t) ~3); } /*如果该key对应的value是最下层,也就是实际存放的就是value,且key后面一定是没有有'.'字符,直接return*/ return value; } //如果寻找不存在,返回这一层key对应的value return hwc->value; }
这个逻辑比较绕,要多加思考,就能融会贯通。当然查找还有ngx_hash_find_wc_tail与其对应。实现大致一样。该函数查询包含通配符在末尾的key的hash表的,我们mail.xxx.*为例,请特别注意通配符在末尾的不像位于开始的通配符可以被省略掉。这样的通配符,可以匹配mail.xxx.com、mail.xxx.com.cn、mail.xxx.net之类的域名。
对于name中的key的初始化
test.com.* -------> test.com te.com.* -------> te.com st.com.* -------> st.com smjd.dff.* -------> smjd.dff
void * ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len) { void *value; ngx_uint_t i, key; key = 0; //从前面开始查找test.*取test for (i = 0; i < len; i++) { if (name[i] == '.') { break; } key = ngx_hash(key, name[i]); } //如果没有字符'.',也就是只剩最后的'*'字符,直接返回给上一层 if (i == len) { return NULL; } //查找test value = ngx_hash_find(&hwc->hash, key, name, i); //如果test存在 if (value) { /* * the 2 low bits of value have the special meaning: * 00 - value is data pointer; * 11 - value is pointer to wildcard hash allowing "example.*". */ //如果对应的value是ngx_hash_wildcard_t if ((uintptr_t) value & 2) { i++; //由于最后一个'*'没有省略,所以key后都是有'.'的 hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3); //递归查找下一层 value = ngx_hash_find_wc_tail(hwc, &name[i], len - i); //如果下一层查找成功 if (value) { return value; } //如果下一层查找没有 return hwc->value; } /*如果是具体的value,已经没有下一层hash表了,存储的具体字符串,最后一个字符不是'.',所以后两位一定是00*/ return value; } //如果test不存在,则返回该层的value return hwc->value; }
最后我们来看一下ngx_hash_combined_t 的查找,结构如下
typedef struct { ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; } ngx_hash_combined_t;
void * ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len) { void *value; //在hash里面查找,如果有,就立即返回value if (hash->hash.buckets) { value = ngx_hash_find(&hash->hash, key, name, len); if (value) { return value; } } if (len == 0) { return NULL; } //head通配符查找。 if (hash->wc_head && hash->wc_head->hash.buckets) { value = ngx_hash_find_wc_head(hash->wc_head, name, len); if (value) { return value; } } //尾部通配符查找 if (hash->wc_tail && hash->wc_tail->hash.buckets) { value = ngx_hash_find_wc_tail(hash->wc_tail, name, len); if (value) { return value; } } return NULL; }
总结
ngx_hash_wildcard_t可以说是对hash表的灵活应用,值得我们多看几遍。相关文章推荐
- 中国大学MOOC-陈越、何钦铭-数据结构基础习题集 00-自测1. 打印沙漏(20)
- 数据结构
- 【暑假】[实用数据结构]UVAlive 4670 Dominating Patterns
- 数据结构(Java语言)——Stack简单实现
- 数据结构-双向链表(学习笔记)
- 数据结构-循环链表(学习笔记)
- nginx的数据结构集合(随时更新)
- C源码@数据结构与算法->队列(queue)
- 什么是算法,什么是数据结构
- 智渔课堂官方免费教程三十七:Java数据结构之单向链表结构
- I学霸官方免费教程三十七:Java数据结构之单向链表结构
- 智渔课堂官方免费教程三十六:Java数据结构之双向链表结构
- I学霸官方免费教程三十六:Java数据结构之双向链表结构
- 快速排序(qsort and sort)
- 【数据结构与算法】八皇后问题之递归
- 数据结构Java实现01----算法概述
- 【数据结构】之二叉树的java实现
- 【数据结构】之链栈的java实现
- 【数据结构】之队列的java实现(二)
- 【数据结构】之队列的java实现(一)