基數樹介紹
基數樹也叫做壓縮前綴樹,是一種多叉搜索樹,對比其他結構跟節省空間。基數樹常見於IP路由檢索,文本文檔的的倒排索引等場景中。同時基數樹也是按照字典順序來組織葉節點的,這種特點使之適合持久化改造,加上他的多道特點,靈活性較強,適合作爲區塊鏈的基礎數據結構,構建持久性區塊時較好的映射各類數據集合。
Nginx基數樹的實現
Nginx中基數樹的實現是一種二叉查找樹,具備二叉查找樹的所有優點,同時避免了紅黑樹增刪數據是需要通過自身旋轉來維持平衡,因此他具有更快的插入、刪除速度和更高的內存空間利用率。基數樹的key兼顧唯一標識和樹平衡維護的功能。的每個節點的key關鍵字先轉換爲32爲二進制數,然後從左至右開始,0進入左子樹,1進入右子樹,節點插入的同時自動完成二叉樹平衡的維護。
1.數據結構
struct ngx_radix_node_s {
ngx_radix_node_t *right;/*右孩子節點*/
ngx_radix_node_t *left;/*左孩子節點*/
ngx_radix_node_t *parent;/*父節點*/
uintptr_t value;/*用戶自定義結構的數據指針*/
};
ngx_radix_tree_t是Nginx基數樹的管理和操作類,實現了內存的自己管理。已經分配但是未使用的節點交給free變量,當需要使用節點的時候優先在free變量中重用已有的節點。
typedef struct {
ngx_radix_node_t *root;/*根節點*/
ngx_pool_t *pool;/*內存池*/
ngx_radix_node_t *free;/*空閒節點*/
char *start;/*已分配內存未使用的首地址,也就是下個節點待分配內存的起始地址*/
size_t size;/*空閒內存的大小*/
} ngx_radix_tree_t;
2.基數樹的創建
基數樹的創建和二叉樹的創建沒有太大的不同,按照基數樹的規則一一實現就好。。Nginx基數樹中爲了減少二叉樹的高度(壓縮前綴樹,壓縮就體現在消除不需要的節點帶來的分支)使用了掩碼。通過掩碼來決定樹的高度,具體邏輯常見代碼註釋。
ngx_radix_tree_t *
ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate)
{
uint32_t key, mask, inc;
ngx_radix_tree_t *tree;
tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));
if (tree == NULL) {
return NULL;
}
tree->pool = pool;
tree->free = NULL;
tree->start = NULL;
tree->size = 0;
tree->root = ngx_radix_alloc(tree);
if (tree->root == NULL) {
return NULL;
}
tree->root->right = NULL;
tree->root->left = NULL;
tree->root->parent = NULL;
tree->root->value = NGX_RADIX_NO_VALUE;
if (preallocate == 0) {
return tree;
}
/*
* Preallocation of first nodes : 0, 1, 00, 01, 10, 11, 000, 001, etc.
* increases TLB hits even if for first lookup iterations.
* On 32-bit platforms the 7 preallocated bits takes continuous 4K,
* 8 - 8K, 9 - 16K, etc. On 64-bit platforms the 6 preallocated bits
* takes continuous 4K, 7 - 8K, 8 - 16K, etc. There is no sense to
* to preallocate more than one page, because further preallocation
* distributes the only bit per page. Instead, a random insertion
* may distribute several bits per page.
*
* Thus, by default we preallocate maximum
* 6 bits on amd64 (64-bit platform and 4K pages)
* 7 bits on i386 (32-bit platform and 4K pages)
* 7 bits on sparc64 in 64-bit mode (8K pages)
* 8 bits on sparc64 in 32-bit mode (8K pages)
*/
/**/
/*1.根據系統電腦處理器架構決定基數樹的最高層數*/
if (preallocate == -1) {
switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {
/* amd64 */
case 128:
preallocate = 6;
break;
/* i386, sparc64 */
case 256:
preallocate = 7;
break;
/* sparc64 in 32-bit mode */
default:
preallocate = 8;
}
}
mask = 0;
inc = 0x80000000;
/*2.掩碼初始0層,然後循環處理器架構層次,掩碼依次右移逐層增加*/
while (preallocate--) {
key = 0;
mask >>= 1;
/*2.1異或0x80000000保證掩碼最高位是1*/
mask |= 0x80000000;/*轉換二進制1000 0000 0000 0000 0000 0000 0000 0000 */
/*2.2基數樹的首先遍歷樹的深度,如果爲1,向右子樹搜索,否則向左子樹搜索,如果找到位置有結點,則直接覆蓋。否則,則依次創建沿途結點(0或1)並插入在樹中。*/
do {
if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
!= NGX_OK)
{
return NULL;
}
key += inc;
} while (key);
inc >>= 1;
}
/*返回構建好的樹*/
return tree;
}
3.基數樹的查找
根據基數樹的規則,從根節點開始遍歷,遇到0遍歷左子樹,遇到1遍歷右子樹,最後返回查詢結果。
uintptr_t
ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key)
{
uint32_t bit;
uintptr_t value;
ngx_radix_node_t *node;
bit = 0x80000000;
value = NGX_RADIX_NO_VALUE;
node = tree->root;
while (node) {
if (node->value != NGX_RADIX_NO_VALUE) {
value = node->value;
}
if (key & bit) {
node = node->right;
} else {
node = node->left;
}
bit >>= 1;
}
return value;
}
Nginx的基數樹要求每個節點key必須可以轉換爲32位整型,並且因爲需要自己管理內存(無形中提高了使用的難度),所以總的來說,即使在Nginx中應用也不廣泛。當然這個也沒什麼奇怪的,基數樹解決的主要問題還是字典問題,而字典問題其實關聯數組,Hash散列表都可以很好的解決這個問題,這樣基數樹不常用也就不難理解了。