菜鳥nginx源碼剖析數據結構篇(五) 基數樹 ngx_radix_tree_tnode
Author:Echo Chen(陳斌)nginx
Email:chenb19870707@gmail.com數據結構
Blog:Blog.csdn.net/chen19870707app
Date:October 28h, 2014dom
基數樹(radix tree)是一種不怎麼常見的數據結構,這裏簡單的作一下介紹:在計算機科學中,基數樹,是一種基於trie(字典樹)的特殊的數據結構,能夠快速定位葉子結點。radix tree是一種多叉搜索樹,每一個結點有固定的孩子數(叉數 爲2^n)。ui
以下圖radix樹的分叉爲4,樹的高度爲4,共有4*4*4*4 = 256 個葉子結點,能夠快速定位256個結點。spa
ngx_radix_tree 是一種二叉查找樹,即叉數爲2,它要求存儲的每一個節點必須以32位整型做爲任意兩節點的惟一標識,ngx_radix_tree 具有二叉查找樹全部優勢,而且不用像紅黑樹經過自身旋轉達到平衡,基數樹不用管樹的形態是否平衡。也所以,它在插入節點、刪除節點的速度會比紅黑樹快的多。.net
基數樹能夠無論樹平衡的緣由在於:紅黑樹是經過不一樣節點key關鍵字的比較決定樹的形態,而基數樹的每一個節點的key關鍵字自身已經決定了其在樹中的位置。先將節點的key關鍵字轉化爲二進制,32位,從左至右開始,遇0入左子樹,遇1入右子樹。指針
ngx_radix_tree_t樹的最大深度爲32,因爲通常用不到這樣的深度,因此引入了掩碼,掩碼中的1的個數就表示樹的高度,掩碼1110 0000 0000 0000 0000 0000 0000 0000 ,表示樹的高度爲3。code
eg:若是此時一個節點的key關鍵字爲0x20000000,根據掩碼決定取其轉化爲二進制後的前3位爲010,所以,該節點的位置是,根節點-->左子樹-->右子樹-->左子樹。用下圖相當表示下:
頭文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_radix_tree.h
源文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_radix_tree.c
結點中left和right分別指向左右孩子,parent指向父親結點,value爲指向用戶自定義的數據的指針。
1: typedef struct ngx_radix_node_s ngx_radix_node_t;
2:
3: struct ngx_radix_node_s {
4: ngx_radix_node_t *right;
5: ngx_radix_node_t *left;
6: ngx_radix_node_t *parent;
7: uintptr_t value;
8: };
與紅黑樹不一樣的是,radix_tree本身管理內存,pool爲內存池對象,root爲根節點,free管理已經分配但暫未使用的節點,free其實是全部不在樹中結點的單鏈表。start爲已分配內存中未使用內存的首地址,size爲已分配內存還未使用內存的大小。
1: typedef struct {
2: ngx_radix_node_t *root;
3: ngx_pool_t *pool;
4: ngx_radix_node_t *free;
5: char *start;
6: size_t size;
7: } ngx_radix_tree_t;
基數樹的構造流程爲首先建立 基數樹結構 ngx_radix_tree_t ,而後建立 基數樹的 root結點,而後根據傳入的preallacate參數來決定預分配結點的個數,若是傳入-1 ,即按照頁面大小決定預分配結點個數,而後就一次插入這些結點。源代碼加註釋以下:
1: //poll爲內存池指針,preallocate是預分配基數樹的節點數目,若是傳-1,那麼將會根據當前系統一個頁的大小來預分配基數樹結點
2: ngx_radix_tree_t *ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate)
3: {
4: uint32_t key, mask, inc;
5: ngx_radix_tree_t *tree;
6:
7: //分配ngx_radix_tree_t
8: tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));
9: if (tree == NULL) {
10: return NULL;
11: }
12:
13: tree->pool = pool;
14: tree->free = NULL;
15: tree->start = NULL;
16: tree->size = 0;
17:
18: //分配根節點
19: tree->root = ngx_radix_alloc(tree);
20: if (tree->root == NULL) {
21: return NULL;
22: }
23:
24: tree->root->right = NULL;
25: tree->root->left = NULL;
26: tree->root->parent = NULL;
27: tree->root->value = NGX_RADIX_NO_VALUE;
28:
29: //若是須要的預分配結點爲0個,完成返回
30: if (preallocate == 0) {
31: return tree;
32: }
33:
34: /*
35: * Preallocation of first nodes : 0, 1, 00, 01, 10, 11, 000, 001, etc.
36: * increases TLB hits even if for first lookup iterations.
37: * On 32-bit platforms the 7 preallocated bits takes continuous 4K,
38: * 8 - 8K, 9 - 16K, etc. On 64-bit platforms the 6 preallocated bits
39: * takes continuous 4K, 7 - 8K, 8 - 16K, etc. There is no sense to
40: * to preallocate more than one page, because further preallocation
41: * distributes the only bit per page. Instead, a random insertion
42: * may distribute several bits per page.
43: *
44: * Thus, by default we preallocate maximum
45: * 6 bits on amd64 (64-bit platform and 4K pages)
46: * 7 bits on i386 (32-bit platform and 4K pages)
47: * 7 bits on sparc64 in 64-bit mode (8K pages)
48: * 8 bits on sparc64 in 32-bit mode (8K pages)
49: */
50:
51: //若是預分配爲-1,則按系統的頁大小預分配頁,如下爲根據頁面大小,肯定preallocate
52: if (preallocate == -1) {
53: switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {
54:
55: /* amd64 */
56: case 128:
57: preallocate = 6;
58: break;
59:
60: /* i386, sparc64 */
61: case 256:
62: preallocate = 7;
63: break;
64:
65: /* sparc64 in 32-bit mode */
66: default:
67: preallocate = 8;
68: }
69: }
70:
71: //inc 的二進制形式爲 1000 0000 0000 0000 0000 0000 0000 0000,逐漸向右移動
72: mask = 0;
73: inc = 0x80000000;
74:
75: //依次插入到基數樹中
76: while (preallocate--) {
77:
78: key = 0;
79: mask >>= 1;
80: mask |= 0x80000000;
81:
82: //沿途一次插入結點
83: do {
84: if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
85: != NGX_OK)
86: {
87: return NULL;
88: }
89:
90: key += inc;
91:
92: } while (key);
93:
94: inc >>= 1;
95: }
96:
97: return tree;
98: }
99:
基數樹的首先遍歷樹的深度,若是爲1,向右子樹搜索,不然向左子樹搜索,若是找到位置有結點,則直接覆蓋。不然,則依次建立沿途結點(0或1)並插入在樹中。
1: //tree爲基數樹,key爲關鍵字,mask爲掩碼
2: ngx_int_t ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask, uintptr_t value)
3: {
4: uint32_t bit;
5: ngx_radix_node_t *node, *next;
6:
7: bit = 0x80000000;
8:
9: node = tree->root;
10: next = tree->root;
11:
12: //遍歷掩碼中1的個數,即爲樹的深度
13: while (bit & mask) {
14: //若是爲1,向右子樹
15: if (key & bit) {
16: next = node->right;
17: //爲0,向左子樹
18: } else {
19: next = node->left;
20: }
21:
22: if (next == NULL) {
23: break;
24: }
25:
26: bit >>= 1;
27: node = next;
28: }
29:
30: //這個位置有結點,直接修改值,返回
31: if (next) {
32: if (node->value != NGX_RADIX_NO_VALUE) {
33: return NGX_BUSY;
34: }
35:
36: node->value = value;
37: return NGX_OK;
38: }
39:
40: //若是樹中沒有結點,依次沿途插入結點
41: while (bit & mask) {
42: next = ngx_radix_alloc(tree);
43: if (next == NULL) {
44: return NGX_ERROR;
45: }
46:
47: next->right = NULL;
48: next->left = NULL;
49: next->parent = node;
50: next->value = NGX_RADIX_NO_VALUE;
51:
52: if (key & bit) {
53: node->right = next;
54:
55: } else {
56: node->left = next;
57: }
58:
59: bit >>= 1;
60: node = next;
61: }
62:
63: node->value = value;
64:
65: return NGX_OK;
66: }
基數樹的刪除遍歷搜索,遍歷基數樹的深度(mask中1 個個數),關鍵字與當前深度爲1,向右;不然向左,若是沒找到,返回。找到了,而且不爲葉子節點,賦值爲無效,返回;若是爲葉子節點,則將其從基數樹中刪除,放入空閒鏈表,並查看其父親結點是否爲一個無效結點,若是也無效,則依次刪除。
1: //tree爲基數樹,key爲要刪除的結點的關鍵字,mask爲掩碼
2: ngx_int_t ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask)
3: {
4: uint32_t bit;
5: ngx_radix_node_t *node;
6:
7: bit = 0x80000000;
8: node = tree->root;
9:
10: //遍歷基數樹的深度(mask中1 個個數)
11: while (node && (bit & mask)) {
12: //關鍵字與當前深度爲1,向右;不然向左
13: if (key & bit) {
14: node = node->right;
15:
16: } else {
17: node = node->left;
18: }
19:
20: bit >>= 1;
21: }
22:
23: //沒找到,返回
24: if (node == NULL) {
25: return NGX_ERROR;
26: }
27:
28: //找到了,而且不爲葉子節點,賦值爲無效,返回
29: if (node->right || node->left) {
30: if (node->value != NGX_RADIX_NO_VALUE) {
31: node->value = NGX_RADIX_NO_VALUE;
32: return NGX_OK;
33: }
34:
35: return NGX_ERROR;
36: }
37:
38: //爲葉子節點
39: for ( ;; ) {
40: //若是在右子樹,從樹中刪除
41: if (node->parent->right == node) {
42: node->parent->right = NULL;
43: //若是在左子樹,從樹中刪除
44: } else {
45: node->parent->left = NULL;
46: }
47:
48: //將該葉子結點連接到空閒鏈表中
49: node->right = tree->free;
50: tree->free = node;
51:
52: //向上迴歸,依次刪除,直至到不能刪除的結點(有有效值的孩子或者本身有有效值)
53: node = node->parent;
54:
55: if (node->right || node->left) {
56: break;
57: }
58:
59: if (node->value != NGX_RADIX_NO_VALUE) {
60: break;
61: }
62:
63: if (node->parent == NULL) {
64: break;
65: }
66: }
67:
68: return NGX_OK;
69: }
1: static ngx_radix_node_t *
2: ngx_radix_alloc(ngx_radix_tree_t *tree)
3: {
4: ngx_radix_node_t *p;
5:
6: //若是空閒鏈表中有結點,取一個返回
7: if (tree->free) {
8: p = tree->free;
9: tree->free = tree->free->right;
10: return p;
11: }
12:
13: //若是空閒鏈表中沒有結點且基數樹中的空閒內存大小不夠分配一個結點,則從內存池中分配一個頁面大小
14: if (tree->size < sizeof(ngx_radix_node_t)) {
15: tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
16: if (tree->start == NULL) {
17: return NULL;
18: }
19:
20: tree->size = ngx_pagesize;
21: }
22:
23: //從未分配內存中分配,並減少size
24: p = (ngx_radix_node_t *) tree->start;
25: tree->start += sizeof(ngx_radix_node_t);
26: tree->size -= sizeof(ngx_radix_node_t);
27:
28: return p;
29: }
基數樹的查找也很簡單,爲1向右,爲0向左。
1: uintptr_t
2: ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key)
3: {
4: uint32_t bit;
5: uintptr_t value;
6: ngx_radix_node_t *node;
7:
8: bit = 0x80000000;
9: value = NGX_RADIX_NO_VALUE;
10: node = tree->root;
11:
12: while (node) {
13: if (node->value != NGX_RADIX_NO_VALUE) {
14: value = node->value;
15: }
16:
17: if (key & bit) {
18: node = node->right;
19:
20: } else {
21: node = node->left;
22: }
23:
24: bit >>= 1;
25: }
26:
27: return value;
28: }