菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)
-
Author:Echo Chen(陈斌)
-
Email:chenb19870707@gmail.com
-
Date:Nov 3rd, 2014
在前面一篇文章《菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)》 里介绍了 普通哈希表 和 带有通配符的哈希表 的基本结构和初始化方法,由于篇幅的原因未能解析完结,这篇继续解源码中剩余的部分。
-
1.普通哈希表ngx_hash_t查找 ngx_hash_find
普通哈希表的查找比较简单,思想就是先根据hash值找到对应桶,然后遍历这个桶的每一个元素,逐字匹配是否关键字完全相同,完全相同则找到,否则继续,直至找到这个桶的结尾(value = NULL)。
1: /* @hash 表示哈希表的结构体
2: * @key 表示根据哈希方法计算出来的hash值
3: * @name 表示实际关键字地址
4: * @len 表示实际关键字长度
5: */
6: void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
7: {
8: ngx_uint_t i;
9: ngx_hash_elt_t *elt;
10:
11: //根据hash值找到桶索引
12: elt = hash->buckets[key % hash->size];
13:
14: if (elt == NULL) {
15: return NULL;
16: }
17:
18: //桶结束的标志为 value 为 NULL
19: while (elt->value) {
20: //关键字长度不匹配,下一个
21: if (len != (size_t) elt->len) {
22: goto next;
23: }
24:
25: //遍历关键字每一个字符,若全部对得上,则找到,否则有一个不同下一个
26: for (i = 0; i < len; i++) {
27: if (name[i] != elt->name[i]) {
28: goto next;
29: }
30: }
31:
32: return elt->value;
33:
34: next:
35: //从elt关键字开始向后移动关键字长度个,并行对齐,即为下一个ngx_hash_elt_t
36: elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
37: sizeof(void *));
38: continue;
39: }
40:
41: return NULL;
42: }
2.支持通配符哈希表的前置通配符查找 ngx_hash_find_wc_head
还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下图所示
- 00 - value 是 "example.com" 和 "*.example.com"的数据指针
- 01 - value 仅仅是 "*.example.com"的数据指针
- 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指针
- 11 - value 仅仅是 "*.example.com"的指针
查找的思路就是根据value的指针信息来搜索,源代码如下:
1: /* @hwc 表示支持通配符的哈希表的结构体
2: * @name 表示实际关键字地址
3: * @len 表示实际关键字长度
4: */
5: void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
6: {
7: void *value;
8: ngx_uint_t i, n, key;
9:
10: n = len;
11:
12: //从后往前搜索第一个dot,则n 到 len-1 即为关键字中最后一个 子关键字
13: while (n) {
14: if (name[n - 1] == '.') {
15: break;
16: }
17:
18: n--;
19: }
20:
21: key = 0;
22:
23: //n 到 len-1 即为关键字中最后一个 子关键字,计算其hash值
24: for (i = n; i < len; i++) {
25: key = ngx_hash(key, name[i]);
26: }
27:
28: //调用普通查找找到关键字的value(用户自定义数据指针)
29: value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);
30:
31: /**还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下:
32: * 00 - value 是 "example.com" 和 "*.example.com"的数据指针
33: * 01 - value 仅仅是 "*.example.com"的数据指针
34: * 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指针
35: * 11 - value 仅仅是 "*.example.com"的指针
36: */
37: if (value)
38: {
39:
40: if ((uintptr_t) value & 2) {
41:
42: //搜索到了最后一个子关键字且没有通配符,如"example.com"的example
43: if (n == 0) {
44: //value低两位为11,仅为"*.example.com"的指针,这里没有通配符,没招到,返回NULL
45: if ((uintptr_t) value & 1) {
46: return NULL;
47: }
48:
49: //value低两位为10,为"example.com"的指针,value就在下一级的ngx_hash_wildcard_t 的value中,去掉携带的低2位11
50: hwc = (ngx_hash_wildcard_t *)
51: ((uintptr_t) value & (uintptr_t) ~3);
52: return hwc->value;
53: }
54:
55: //还未搜索完,低两位为11或10,继续去下级ngx_hash_wildcard_t中搜索
56: hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
57:
58: //继续搜索 关键字中剩余部分,如"example.com",搜索 0 到 n -1 即为 example
59: value = ngx_hash_find_wc_head(hwc, name, n - 1);
60:
61: //若找到,则返回
62: if (value)
63: {
64: return value;
65: }
66:
67: //低两位为00 找到,即为wc->value
68: return hwc->value;
69: }
70:
71: //低两位为01
72: if ((uintptr_t) value & 1)
73: {
74: //关键字没有通配符,错误返回空
75: if (n == 0)
76: {
77: return NULL;
78: }
79:
80: //有通配符,直接返回
81: return (void *) ((uintptr_t) value & (uintptr_t) ~3);
82: }
83:
84: //低两位为00,直接返回
85: return value;
86: }
87:
88: return hwc->value;
89: }
3.支持通配符哈希表的后置通配符查找 ngx_hash_find_wc_tail
ngx_hash_find_wc_tail与前置通配符查找差不多,这里value低两位仅有两种标志,更加简单:
- 00 - value 是指向 用户自定义数据
- 11 - value的指向下一个哈希表
源代码如下:
1: void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
2: {
3: void *value;
4: ngx_uint_t i, key;
5:
6:
7: key = 0;
8:
9: //从前往前搜索第一个dot,则0 到 i 即为关键字中第一个 子关键字
10: for (i = 0; i < len; i++)
11: {
12: if (name[i] == '.')
13: {
14: break;
15: }
16: //计算哈希值
17: key = ngx_hash(key, name[i]);
18: }
19:
20: //没有通配符,返回NULL
21: if (i == len)
22: {
23: return NULL;
24: }
25:
26: //调用普通查找找到关键字的value(用户自定义数据指针)
27: value = ngx_hash_find(&hwc->hash, key, name, i);
28:
29:
30: /**还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下:
31: * 00 - value 是数据指针
32: * 11 - value的指向下一个哈希表
33: */
34: if (value)
35: {
36: //低2位为11,value的指向下一个哈希表,递归搜索
37: if ((uintptr_t) value & 2)
38: {
39:
40: i++;
41:
42: hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
43:
44: value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
45:
46: //找到低两位00,返回
47: if (value)
48: {
49: return value;
50: }
51:
52: //找打低两位11,返回hwc->value
53: return hwc->value;
54: }
55:
56: return value;
57: }
58:
59: //低2位为00,直接返回数据
60: return hwc->value;
61: }
4.组合哈希表查找 ngx_hash_find_combined
组合哈希表的查找思路非常简单,先在普通哈希表中查找,没找到再去前置通配符哈希表中查找,最后去后置通配符哈希表中查找,源代码如下:
1: void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name,size_t len)
2: {
3: void *value;
4:
5: //在普通hash表中查找
6: if (hash->hash.buckets) {
7: value = ngx_hash_find(&hash->hash, key, name, len);
8:
9: if (value) {
10: return value;
11: }
12: }
13:
14: if (len == 0) {
15: return NULL;
16: }
17:
18: //在前置通配符哈希表中查找
19: if (hash->wc_head && hash->wc_head->hash.buckets) {
20: value = ngx_hash_find_wc_head(hash->wc_head, name, len);
21:
22: if (value) {
23: return value;
24: }
25: }
26:
27: //在后置通配符哈希表中查找
28: if (hash->wc_tail && hash->wc_tail->hash.buckets) {
29: value = ngx_hash_find_wc_tail(hash->wc_tail, name, len);
30:
31: if (value) {
32: return value;
33: }
34: }
35:
36: return NULL;
37: }
5.支持通配符哈希表初始化 ngx_hash_wildcard_init
首先看一下ngx_hash_wildcard_init 的内存结构,当构造此类型的hash表的时候,实际上是构造了一个hash表的一个“链表”,是通过hash表中的key“链接”起来的。比如:对于“*.abc.com”将会构造出2个hash表,第一个hash表中有一个key为com的表项,该表项的value包含有指向第二个hash表的指针,而第二个hash表中有一个表项abc,该表项的value包含有指向*.abc.com对应的value的指针。那么查询的时候,比如查询www.abc.com的时候,先查com,通过查com可以找到第二级的hash表,在第二级hash表中,再查找abc,依次类推,直到在某一级的hash表中查到的表项对应的value对应一个真正的值而非一个指向下一级hash表的指针的时候,查询过程结束。
理解了这个,我们就可以看源代码了,ngx_hash_wildcard是一个递归函数,递归创建如上图的hash链表,如下为注释版源代码。
精彩的读点有:
- 由于指针都字节对齐了,低4位肯定为0,这种操作(name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2)) ) 巧妙的使用了指针的低位携带额外信息,节省了内存,让人不得不佩服ngx设计者的想象力。
1: /*hinit为初始化结构体指针,names为预加入哈希表数组,elts为预加入数组大小
2: 特别要注意的是这里的key已经都是被预处理过的。例如:“*.abc.com”或者“.abc.com”被预处理完成以后,
3: 变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”*/
4: ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,ngx_uint_t nelts)
5: {
6: size_t len, dot_len;
7: ngx_uint_t i, n, dot;
8: ngx_array_t curr_names, next_names;
9: ngx_hash_key_t *name, *next_name;
10: ngx_hash_init_t h;
11: ngx_hash_wildcard_t *wdc;
12:
13: //初始化临时动态数组curr_names,curr_names是存放当前关键字的数组
14: if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
15: sizeof(ngx_hash_key_t))
16: != NGX_OK)
17: {
18: return NGX_ERROR;
19: }
20:
21: //初始化临时动态数组next_names,next_names是存放关键字去掉后剩余关键字
22: if (ngx_array_init(&next_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK)
23: {
24: return NGX_ERROR;
25: }
26:
27: //遍历 names 数组
28: for (n = 0; n < nelts; n = i)
29: {
30: dot = 0;
31:
32: //查找 dot
33: for (len = 0; len < names[n].key.len; len++)
34: {
35: if (names[n].key.data[len] == '.')
36: {
37: dot = 1;
38: break;
39: }
40: }
41:
42: //将关键字dot以前的关键字放入curr_names
43: name = ngx_array_push(&curr_names);
44: if (name == NULL) {
45: return NGX_ERROR;
46: }
47:
48: name->key.len = len;
49: name->key.data = names[n].key.data;
50: name->key_hash = hinit->key(name->key.data, name->key.len);
51: name->value = names[n].value;
52:
53: dot_len = len + 1;
54:
55: //len指向dot后剩余关键字
56: if (dot)
57: {
58: len++;
59: }
60:
61: next_names.nelts = 0;
62:
63: //如果names[n] dot后还有剩余关键字,将剩余关键字放入next_names中
64: if (names[n].key.len != len)
65: {
66: next_name = ngx_array_push(&next_names);
67: if (next_name == NULL) {
68: return NGX_ERROR;
69: }
70:
71: next_name->key.len = names[n].key.len - len;
72: next_name->key.data = names[n].key.data + len;
73: next_name->key_hash = 0;
74: next_name->value = names[n].value;
75:
76: }
77:
78: //如果上面搜索到的关键字没有dot,从n+1遍历names,将关键字比它长的全部放入next_name
79: for (i = n + 1; i < nelts; i++)
80: {
81: //前len个关键字相同
82: if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0) {
83: break;
84: }
85:
86:
87: if (!dot
88: && names[i].key.len > len
89: && names[i].key.data[len] != '.')
90: {
91: break;
92: }
93:
94: next_name = ngx_array_push(&next_names);
95: if (next_name == NULL) {
96: return NGX_ERROR;
97: }
98:
99: next_name->key.len = names[i].key.len - dot_len;
100: next_name->key.data = names[i].key.data + dot_len;
101: next_name->key_hash = 0;
102: next_name->value = names[i].value;
103:
104: }
105:
106: //如果next_name非空
107: if (next_names.nelts)
108: {
109: h = *hinit;
110: h.hash = NULL;
111:
112: //递归,创建一个新的哈西表
113: if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,next_names.nelts) != NGX_OK)
114: {
115: return NGX_ERROR;
116: }
117:
118: wdc = (ngx_hash_wildcard_t *) h.hash;
119:
120: //如上图,将用户value值放入新的hash表
121: if (names[n].key.len == len)
122: {
123: wdc->value = names[n].value;
124: }
125:
126: //并将当前value值指向新的hash表
127: name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
128:
129: } else if (dot)
130: {
131: name->value = (void *) ((uintptr_t) name->value | 1);
132: }
133: }
134:
135: //将最外层hash初始化
136: if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts,curr_names.nelts) != NGX_OK)
137: {
138: return NGX_ERROR;
139: }
140:
141: return NGX_OK;
142: }
6. 哈希键数组初始化 ngx_hash_keys_array_init
初始化ngx_hash_keys_arrays_t 结构体,type的取值范围只有两个,NGX_HASH_SMALL表示初始化元素较少,NGX_HASH_LARGE表示初始化元素较多,在向ha中加入时必须调用此方法。
1: ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
2: {
3: ngx_uint_t asize;
4:
5: if (type == NGX_HASH_SMALL)
6: {
7: asize = 4;
8: ha->hsize = 107;
9: }
10: else
11: {
12: asize = NGX_HASH_LARGE_ASIZE;
13: ha->hsize = NGX_HASH_LARGE_HSIZE;
14: }
15:
16: //初始化 存放非通配符关键字的数组
17: if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
18: {
19: return NGX_ERROR;
20: }
21:
22: //初始化 存放前置通配符处理好的关键字 数组
23: if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
24: {
25: return NGX_ERROR;
26: }
27:
28: //初始化 存放后置通配符处理好的关键字 数组
29: if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))!= NGX_OK)
30: {
31: return NGX_ERROR;
32: }
33:
34: /*初始化 二位数组 ,这个数组存放的第一个维度代表的是bucket的编号,
35: 那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
36: 假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
37: 那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
38: 该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
39: ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
40: if (ha->keys_hash == NULL)
41: {
42: return NGX_ERROR;
43: }
44:
45: // 该数组在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
46: ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,sizeof(ngx_array_t) * ha->hsize);
47:
48: if (ha->dns_wc_head_hash == NULL)
49: {
50: return NGX_ERROR;
51: }
52:
53: // 该数组在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
54: ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
55: if (ha->dns_wc_tail_hash == NULL)
56: {
57: return NGX_ERROR;
58: }
59:
60: return NGX_OK;
61: }
7. 向ngx_hash_keys_array中添加关键字
1: ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
2: {
3: ngx_uint_t asize;
4:
5: if (type == NGX_HASH_SMALL)
6: {
7: asize = 4;
8: ha->hsize = 107;
9: }
10: else
11: {
12: asize = NGX_HASH_LARGE_ASIZE;
13: ha->hsize = NGX_HASH_LARGE_HSIZE;
14: }
15:
16: //初始化 存放非通配符关键字的数组
17: if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
18: {
19: return NGX_ERROR;
20: }
21:
22: //初始化 存放前置通配符处理好的关键字 数组
23: if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
24: {
25: return NGX_ERROR;
26: }
27:
28: //初始化 存放后置通配符处理好的关键字 数组
29: if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))!= NGX_OK)
30: {
31: return NGX_ERROR;
32: }
33:
34: /*初始化 二位数组 ,这个数组存放的第一个维度代表的是bucket的编号,
35: 那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
36: 假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
37: 那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
38: 该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
39: ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
40: if (ha->keys_hash == NULL)
41: {
42: return NGX_ERROR;
43: }
44:
45: // 该数组在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
46: ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,sizeof(ngx_array_t) * ha->hsize);
47:
48: if (ha->dns_wc_head_hash == NULL)
49: {
50: return NGX_ERROR;
51: }
52:
53: // 该数组在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
54: ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
55: if (ha->dns_wc_tail_hash == NULL)
56: {
57: return NGX_ERROR;
58: }
59:
60: return NGX_OK;
61: }
-