基数树介绍
基数树也叫做压缩前缀树,是一种多叉搜索树,对比其他结构跟节省空间。基数树常见于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散列表都可以很好的解决这个问题,这样基数树不常用也就不难理解了。