本节研究基数树相关的机制和实现;
基数树
说明几点
(1)基数树,是一种基于二进制表示键值的二叉查找树,类似字典树;其典型应用为IP地址的查找;
(2)如果使用IPv4时,基数树只需要支持到最大深度为32就可以了,key值从最高位向最低位开始匹配,比如key为0xC0000000,将会从key的最高位1向0开始匹配;
代码分析
基数树声明
//基数树节点 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,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 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
//查找对应节点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; }原文链接:https://www.f2er.com/datastructure/382662.html