标签:
本节研究基数树相关的机制和实现;
说明几点
(1)基数树,是一种基于二进制表示键值的二叉查找树,类似字典树;其典型应用为IP地址的查找;
(2)如果使用IPv4时,基数树只需要支持到最大深度为32就可以了,key值从最高位向最低位开始匹配,比如key为0xC0000000,将会从key的最高位1向0开始匹配;
(本节代码选自Nginx中关于基数树的代码)
//基数树节点
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_NO_VALUE
};
//基数树管理结构
typedef struct {
ngx_radix_node_t *root; //指向根结点
ngx_pool_t *pool; //内存池
ngx_radix_node_t *free; //管理已经分配但暂时未使用(不在树中)的节点,实际上所有不在树中节点的单链表,使用right组成一个单链表
char *start; //管理已经分配但暂时未使用内存的首地址
size_t size; //已经分配内存中还未使用的内存大小
} ngx_radix_tree_t;//基数树节点分配(申请一个节点内存)
static ngx_radix_node_t *
ngx_radix_alloc(ngx_radix_tree_t *tree)
{
ngx_radix_node_t *p;
if (tree->free) { //管理已经分配但暂时未使用(不在树中)的节点有节点
p = tree->free; //直接找到
tree->free = tree->free->right; //指向下一个节点
return p;
}
if (tree->size < sizeof(ngx_radix_node_t)) { //已有内存不够分配一个节点
tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize); //分配一页内存
if (tree->start == NULL) {
return NULL;
}
tree->size = ngx_pagesize; //一页大小
}
p = (ngx_radix_node_t *) tree->start; //分配的节点内存地址
tree->start += sizeof(ngx_radix_node_t); //更新已分配内存还未使用内存的地址
tree->size -= sizeof(ngx_radix_node_t); //剩余的内存大小
return p;
}//基数树创建
ngx_radix_tree_t *
ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate)
{
//preallocate表示建树的深度;当preallocate为1时,一共有3个节点;当preallocate为2,一共有7个节点;当为n时有2^(n+1)-1个节点;默认为-1;
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; //管理已经分配但暂时未使用(不在树中)的节点,初始化为NULL
tree->start = NULL; //管理已经分配但暂时未使用内存的首地址,初始化为NULL
tree->size = 0; //已经分配内存中还未使用的内存大小,初始化为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)
*/
if (preallocate == -1) {
switch (ngx_pagesize / sizeof(ngx_radix_node_t)) { //可以有多少个树节点
/* amd64 */
case 128:
preallocate = 6; // 2^7-1为127个节点
break;
/* i386, sparc64 */
case 256:
preallocate = 7; // 2^8-1为255个节点
break;
/* sparc64 in 32-bit mode */
default:
preallocate = 8;
}
}
mask = 0;
inc = 0x80000000; //增加的幅度
//预创建树
while (preallocate--) {
key = 0;
mask >>= 1;
mask |= 0x80000000; //初始时为0x80000000,表示第一层;然后依次右移和或,变为0xC0000000,表示第二层;其实mask表示对应的层次
do {
if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
!= NGX_OK)
{
return NULL;
}
key += inc;
//inc为0x80000000时,mask为0x80000000(表示第一层),key表示创建的节点值,根节点为0,然后变为0x80000000这样就创建了根结点的左右孩子;
//退出循环后,inc为0x40000000时,mask为0xC0000000(表示第2层)key为依次又变为0,然后到0x40000000,然后到0x80000000,
//然后到0xC0000000,然后到0x00000000,这样又对应创建了4个孩子;
} while (key);
inc >>= 1;
}
return tree;
}//插入基数树节点,使用mask来控制层次,避免建立额外的层次
//预创建,将会建立一颗满二叉树;如果不用预创建,只会创建对应路径的树节点,其他分支则不会创建;
ngx_int_t
ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask,
uintptr_t value)
{
uint32_t bit;
ngx_radix_node_t *node, *next;
bit = 0x80000000;
node = tree->root; //头节点
next = tree->root;
while (bit & mask) { //一位位判断,从高到低,mask控制移动的层次
if (key & bit) { //对应的位为1,向有查找,初始时,key为0直接break;
next = node->right;
} else {
next = node->left; //为0向左找
}
if (next == NULL) { //跳出,已经找到
break;
}
bit >>= 1; //向低位移动
node = next; //指向父节点
}
if (next) { //找到指定层的节点时,若不为空时
if (node->value != NGX_RADIX_NO_VALUE) {
return NGX_BUSY;
}
node->value = value; //可以赋值
return NGX_OK;
}
while (bit & mask) { //一位位判断,从高到低,mask控制移动的层次
next = ngx_radix_alloc(tree); //申请一个基数树节点
if (next == NULL) {
return NGX_ERROR;
}
next->right = NULL;
next->left = NULL;
next->parent = node; //指向父节点
next->value = NGX_RADIX_NO_VALUE; //初始化为无效值
if (key & bit) {
node->right = next;
} else {
node->left = next;
}
bit >>= 1; //bit继续右移
node = next;
}
node->value = value; //对应的叶子节点,指向对应的值value
return NGX_OK;
}
//删除基数树节点
ngx_int_t
ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask)
{
uint32_t bit;
ngx_radix_node_t *node;
bit = 0x80000000;
node = tree->root;
while (node && (bit & mask)) { //mask控制层次
if (key & bit) {
node = node->right;
} else {
node = node->left;
}
bit >>= 1;
}
if (node == NULL) {
return NGX_ERROR;
}
//不为叶子节点时,简单处理
if (node->right || node->left) { //找到指定的节点了,但左右孩子有任一不为空时
if (node->value != NGX_RADIX_NO_VALUE) {
node->value = NGX_RADIX_NO_VALUE; //直接赋值为无效
return NGX_OK;
}
return NGX_ERROR;
}
//为叶子节点时,回收内存,回收一系列单路径节点内存
for ( ;; ) {
if (node->parent->right == node) { //该节点对应的孩子指向为空
node->parent->right = NULL;
} else {
node->parent->left = NULL;
}
//插入到tree->free的头部
node->right = tree->free;
tree->free = node;
node = node->parent; //指向父亲
if (node->right || node->left) { //父亲还有另外的孩子,直接退出
break;
}
if (node->value != NGX_RADIX_NO_VALUE) { //父亲的值还是有效的,直接退出
break;
}
if (node->parent == NULL) { //如果父亲是根结点,那么也直接退出,根节点不删除
break;
}
}
return NGX_OK;
}
//查找对应节点key上的值,其实是得到最长的匹配,不需要mask来控制层次
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) { //为1时,表示向右走,到右孩子
node = node->right;
} else { //为0时,表示向左走,到左孩子
node = node->left;
}
bit >>= 1;
}
return value;
}
标签:
原文地址:http://blog.csdn.net/skyuppour/article/details/45849461