菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t

  • Author:Echo Chen(陈斌)

  • Email:chenb19870707@gmail.com

  • Blog:Blog.csdn.net/chen19870707

  • Date:October 28h, 2014

    1.什么是基数树

    基数树(radix tree)是一种不怎么常见的数据结构,这里简单的做一下介绍:在计算机科学中,基数树,是一种基于trie(字典树)的特殊的数据结构,可以快速定位叶子结点。radix tree是一种多叉搜索树,每个结点有固定的孩子数(叉数 为2^n)。

    如下图radix树的分叉为4,树的高度为4,共有4*4*4*4 = 256 个叶子结点,可以快速定位256个结点。

    2.ngx_radix_tree_t

    ngx_radix_tree 是一种二叉查找树,即叉数为2,它要求存储的每个节点必须以32位整型作为任意两节点的唯一标识,ngx_radix_tree 具备二叉查找树所有优点,并且不用像红黑树通过自身旋转达到平衡,基数树不用管树的形态是否平衡。也因此,它在插入节点、删除节点的速度会比红黑树快的多。

    基数树可以不管树平衡的原因在于:红黑树是通过不同节点key关键字的比较决定树的形态,而基数树的每个节点的key关键字自身已经决定了其在树中的位置。先将节点的key关键字转化为二进制,32位,从左至右开始,遇0入左子树,遇1入右子树。

    ngx_radix_tree_t树的最大深度为32,由于一般用不到这样的深度,所以引入了掩码,掩码中的1的个数就表示树的高度,掩码1110 0000 0000 0000 0000 0000 0000 0000 ,表示树的高度为3。

    eg:如果此时一个节点的key关键字为0x20000000,根据掩码决定取其转化为二进制后的前3位为010,因此,该节点的位置是,根节点-->左子树-->右子树-->左子树。用下图至关表示下:

    3.源代码位置

    头文件: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

    4.数据结构定义

    结点中left和right分别指向左右孩子,parent指向父亲结点,value为指向用户自定义的数据的指针。

    1. 1: typedef struct ngx_radix_node_s  ngx_radix_node_t;
    1. 2: 
    1. 3: struct ngx_radix_node_s {
    1. 4:     ngx_radix_node_t  *right;
    1. 5:     ngx_radix_node_t  *left;
    1. 6:     ngx_radix_node_t  *parent;
    1. 7:     uintptr_t          value;
    1. 8: };

    与红黑树不同的是,radix_tree自己管理内存,pool为内存池对象,root为根节点,free管理已经分配但暂未使用的节点,free实际上是所有不在树中结点的单链表。start为已分配内存中未使用内存的首地址,size为已分配内存还未使用内存的大小。

    1. 1: typedef struct {
    1. 2:     ngx_radix_node_t  *root;
    1. 3:     ngx_pool_t        *pool;
    1. 4:     ngx_radix_node_t  *free;
    1. 5:     char              *start;
    1. 6:     size_t             size;
    1. 7: } ngx_radix_tree_t;

    5.基数树的创建ngx_radix_tree_create

    基数树的构造流程为首先创建 基数树结构 ngx_radix_tree_t ,然后创建 基数树的 root结点,然后根据传入的preallacate参数来决定预分配结点的个数,如果传入-1 ,即按照页面大小决定预分配结点个数,然后就一次插入这些结点。源代码加注释如下:

    1. 1: //poll为内存池指针,preallocate是预分配基数树的节点数目,如果传-1,那么将会根据当前系统一个页的大小来预分配基数树结点
    1. 2: ngx_radix_tree_t *ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate)
    1. 3: {
    1. 4:     uint32_t           key, mask, inc;
    1. 5:     ngx_radix_tree_t  *tree;
    1. 6: 
    1. 7:     //分配ngx_radix_tree_t
    1. 8:     tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));
    1. 9:     if (tree == NULL) {
    1. 10:         return NULL;
    1. 11:     }
    1. 12: 
    1. 13:     tree->pool = pool;
    1. 14:     tree->free = NULL;
    1. 15:     tree->start = NULL;
    1. 16:     tree->size = 0;
    1. 17: 
    1. 18:     //分配根节点
    1. 19:     tree->root = ngx_radix_alloc(tree);
    1. 20:     if (tree->root == NULL) {
    1. 21:         return NULL;
    1. 22:     }
    1. 23: 
    1. 24:     tree->root->right = NULL;
    1. 25:     tree->root->left = NULL;
    1. 26:     tree->root->parent = NULL;
    1. 27:     tree->root->value = NGX_RADIX_NO_VALUE;
    1. 28: 
    1. 29:     //如果需要的预分配结点为0个,完成返回
    1. 30:     if (preallocate == 0) {
    1. 31:         return tree;
    1. 32:     }
    1. 33: 
    1. 34:     /*
    1. 35:      * Preallocation of first nodes : 0, 1, 00, 01, 10, 11, 000, 001, etc.
    1. 36:      * increases TLB hits even if for first lookup iterations.
    1. 37:      * On 32-bit platforms the 7 preallocated bits takes continuous 4K,
    1. 38:      * 8 - 8K, 9 - 16K, etc.  On 64-bit platforms the 6 preallocated bits
    1. 39:      * takes continuous 4K, 7 - 8K, 8 - 16K, etc.  There is no sense to
    1. 40:      * to preallocate more than one page, because further preallocation
    1. 41:      * distributes the only bit per page.  Instead, a random insertion
    1. 42:      * may distribute several bits per page.
    1. 43:      *
    1. 44:      * Thus, by default we preallocate maximum
    1. 45:      *     6 bits on amd64 (64-bit platform and 4K pages)
    1. 46:      *     7 bits on i386 (32-bit platform and 4K pages)
    1. 47:      *     7 bits on sparc64 in 64-bit mode (8K pages)
    1. 48:      *     8 bits on sparc64 in 32-bit mode (8K pages)
    1. 49:      */
    1. 50: 
    1. 51:     //如果预分配为-1,则按系统的页大小预分配页,以下为根据页面大小,确定preallocate
    1. 52:     if (preallocate == -1) {
    1. 53:         switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {
    1. 54: 
    1. 55:         /* amd64 */
    1. 56:         case 128:
    1. 57:             preallocate = 6;
    1. 58:             break;
    1. 59: 
    1. 60:         /* i386, sparc64 */
    1. 61:         case 256:
    1. 62:             preallocate = 7;
    1. 63:             break;
    1. 64: 
    1. 65:         /* sparc64 in 32-bit mode */
    1. 66:         default:
    1. 67:             preallocate = 8;
    1. 68:         }
    1. 69:     }
    1. 70: 
    1. 71:     //inc 的二进制形式为 1000 0000 0000 0000 0000 0000 0000 0000,逐渐向右移动
    1. 72:     mask = 0;
    1. 73:     inc = 0x80000000;
    1. 74: 
    1. 75:     //依次插入到基数树中
    1. 76:     while (preallocate--) {
    1. 77: 
    1. 78:         key = 0;
    1. 79:         mask >>= 1;
    1. 80:         mask |= 0x80000000;
    1. 81:        
    1. 82:         //沿途一次插入结点
    1. 83:         do {
    1. 84:             if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
    1. 85:                 != NGX_OK)
    1. 86:             {
    1. 87:                 return NULL;
    1. 88:             }
    1. 89: 
    1. 90:             key += inc;
    1. 91: 
    1. 92:         } while (key);
    1. 93: 
    1. 94:         inc >>= 1;
    1. 95:     }
    1. 96: 
    1. 97:     return tree;
    1. 98: }
    1. 99: 

    6.基数树插入操作ngx_radix_tree_insert

    基数树的首先遍历树的深度,如果为1,向右子树搜索,否则向左子树搜索,如果找到位置有结点,则直接覆盖。否则,则依次创建沿途结点(0或1)并插入在树中。

    1. 1: //tree为基数树,key为关键字,mask为掩码
    1. 2: ngx_int_t ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask, uintptr_t value)
    1. 3: {
    1. 4:     uint32_t           bit;
    1. 5:     ngx_radix_node_t  *node, *next;
    1. 6: 
    1. 7:     bit = 0x80000000;
    1. 8: 
    1. 9:     node = tree->root;
    1. 10:     next = tree->root;
    1. 11:    
    1. 12:     //遍历掩码中1的个数,即为树的深度
    1. 13:     while (bit & mask) {
    1. 14:         //如果为1,向右子树
    1. 15:         if (key & bit) {
    1. 16:             next = node->right;
    1. 17:         //为0,向左子树
    1. 18:         } else {
    1. 19:             next = node->left;
    1. 20:         }
    1. 21: 
    1. 22:         if (next == NULL) {
    1. 23:             break;
    1. 24:         }
    1. 25: 
    1. 26:         bit >>= 1;
    1. 27:         node = next;
    1. 28:     }
    1. 29:    
    1. 30:     //这个位置有结点,直接修改值,返回
    1. 31:     if (next) {
    1. 32:         if (node->value != NGX_RADIX_NO_VALUE) {
    1. 33:             return NGX_BUSY;
    1. 34:         }
    1. 35: 
    1. 36:         node->value = value;
    1. 37:         return NGX_OK;
    1. 38:     }
    1. 39:    
    1. 40:     //如果树中没有结点,依次沿途插入结点
    1. 41:     while (bit & mask) {
    1. 42:         next = ngx_radix_alloc(tree);
    1. 43:         if (next == NULL) {
    1. 44:             return NGX_ERROR;
    1. 45:         }
    1. 46: 
    1. 47:         next->right = NULL;
    1. 48:         next->left = NULL;
    1. 49:         next->parent = node;
    1. 50:         next->value = NGX_RADIX_NO_VALUE;
    1. 51: 
    1. 52:         if (key & bit) {
    1. 53:             node->right = next;
    1. 54: 
    1. 55:         } else {
    1. 56:             node->left = next;
    1. 57:         }
    1. 58: 
    1. 59:         bit >>= 1;
    1. 60:         node = next;
    1. 61:     }
    1. 62: 
    1. 63:     node->value = value;
    1. 64: 
    1. 65:     return NGX_OK;
    1. 66: }

    7.基数树删除操作ngx_radix_tree_delete

    基数树的删除遍历搜索,遍历基数树的深度(mask中1 个个数),关键字与当前深度为1,向右;否则向左,如果没找到,返回。找到了,并且不为叶子节点,赋值为无效,返回;如果为叶子节点,则将其从基数树中删除,放入空闲链表,并查看其父亲结点是否为一个无效结点,如果也无效,则依次删除。

    1. 1: //tree为基数树,key为要删除的结点的关键字,mask为掩码
    1. 2: ngx_int_t ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask)
    1. 3: {
    1. 4:     uint32_t           bit;
    1. 5:     ngx_radix_node_t  *node;
    1. 6: 
    1. 7:     bit = 0x80000000;
    1. 8:     node = tree->root;
    1. 9:    
    1. 10:     //遍历基数树的深度(mask中1 个个数)
    1. 11:     while (node && (bit & mask)) {
    1. 12:         //关键字与当前深度为1,向右;否则向左
    1. 13:         if (key & bit) {
    1. 14:             node = node->right;
    1. 15: 
    1. 16:         } else {
    1. 17:             node = node->left;
    1. 18:         }
    1. 19: 
    1. 20:         bit >>= 1;
    1. 21:     }
    1. 22:    
    1. 23:     //没找到,返回
    1. 24:     if (node == NULL) {
    1. 25:         return NGX_ERROR;
    1. 26:     }
    1. 27:    
    1. 28:     //找到了,并且不为叶子节点,赋值为无效,返回
    1. 29:     if (node->right || node->left) {
    1. 30:         if (node->value != NGX_RADIX_NO_VALUE) {
    1. 31:             node->value = NGX_RADIX_NO_VALUE;
    1. 32:             return NGX_OK;
    1. 33:         }
    1. 34: 
    1. 35:         return NGX_ERROR;
    1. 36:     }
    1. 37:    
    1. 38:     //为叶子节点
    1. 39:     for ( ;; ) {
    1. 40:         //如果在右子树,从树中删除
    1. 41:         if (node->parent->right == node) {
    1. 42:             node->parent->right = NULL;
    1. 43:         //如果在左子树,从树中删除
    1. 44:         } else {
    1. 45:             node->parent->left = NULL;
    1. 46:         }
    1. 47:        
    1. 48:         //将该叶子结点链接到空闲链表中
    1. 49:         node->right = tree->free;
    1. 50:         tree->free = node;
    1. 51:        
    1. 52:         //向上回归,依次删除,直至到不能删除的结点(有有效值的孩子或者自己有有效值)
    1. 53:         node = node->parent;
    1. 54: 
    1. 55:         if (node->right || node->left) {
    1. 56:             break;
    1. 57:         }
    1. 58: 
    1. 59:         if (node->value != NGX_RADIX_NO_VALUE) {
    1. 60:             break;
    1. 61:         }
    1. 62: 
    1. 63:         if (node->parent == NULL) {
    1. 64:             break;
    1. 65:         }
    1. 66:     }
    1. 67: 
    1. 68:     return NGX_OK;
    1. 69: }

    8.基数树内存分配ngx_radix_tree_alloc

    1. 1: static ngx_radix_node_t *
    1. 2: ngx_radix_alloc(ngx_radix_tree_t *tree)
    1. 3: {
    1. 4:     ngx_radix_node_t  *p;
    1. 5:    
    1. 6:     //如果空闲链表中有结点,取一个返回
    1. 7:     if (tree->free) {
    1. 8:         p = tree->free;
    1. 9:         tree->free = tree->free->right;
    1. 10:         return p;
    1. 11:     }
    1. 12:    
    1. 13:     //如果空闲链表中没有结点且基数树中的空闲内存大小不够分配一个结点,则从内存池中分配一个页面大小
    1. 14:     if (tree->size < sizeof(ngx_radix_node_t)) {
    1. 15:         tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
    1. 16:         if (tree->start == NULL) {
    1. 17:             return NULL;
    1. 18:         }
    1. 19: 
    1. 20:         tree->size = ngx_pagesize;
    1. 21:     }
    1. 22:    
    1. 23:     //从未分配内存中分配,并减小size
    1. 24:     p = (ngx_radix_node_t *) tree->start;
    1. 25:     tree->start += sizeof(ngx_radix_node_t);
    1. 26:     tree->size -= sizeof(ngx_radix_node_t);
    1. 27: 
    1. 28:     return p;
    1. 29: }

    9.基数树查找ngx_radix32tree_find

    基数树的查找也很简单,为1向右,为0向左。

    1. 1: uintptr_t
    1. 2: ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key)
    1. 3: {
    1. 4:     uint32_t           bit;
    1. 5:     uintptr_t          value;
    1. 6:     ngx_radix_node_t  *node;
    1. 7: 
    1. 8:     bit = 0x80000000;
    1. 9:     value = NGX_RADIX_NO_VALUE;
    1. 10:     node = tree->root;
    1. 11: 
    1. 12:     while (node) {
    1. 13:         if (node->value != NGX_RADIX_NO_VALUE) {
    1. 14:             value = node->value;
    1. 15:         }
    1. 16: 
    1. 17:         if (key & bit) {
    1. 18:             node = node->right;
    1. 19: 
    1. 20:         } else {
    1. 21:             node = node->left;
    1. 22:         }
    1. 23: 
    1. 24:         bit >>= 1;
    1. 25:     }
    1. 26: 
    1. 27:     return value;
    1. 28: }

菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t[转]的更多相关文章

  1. 菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t[转]

    菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

  2. 菜鸟nginx源码剖析数据结构篇(十) 自旋锁ngx_spinlock[转]

    菜鸟nginx源码剖析数据结构篇(十) 自旋锁ngx_spinlock Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

  3. 菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t[转]

    菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn. ...

  4. 菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表ngx_chain_t[转]

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

  5. 菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)[转]

    菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

  6. 菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)[转]

    菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

  7. 菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t[转]

    菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

  8. 菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t[转]

    菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

  9. 菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t[转]

    菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

随机推荐

  1. Java 核心编程技术干货,2019 最新整理版!

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新. Java 基础篇 ...

  2. parallels desktop虚拟机与Mac共享网络设置方法

    查看vnic0的ip centos7设置ip parallels desktop偏好设置 最后可以互ping 也可以ping外网

  3. 【POJ】2253 Frogger

    = =.请用C++提交.. 如果有朋友能告诉我G++和C++交题什么机制..我感激不尽.G++杀我. 题目链接:http://poj.org/problem?id=2253 题意:青蛙A要去找B约会, ...

  4. JS事件 鼠标移开事件(onmouseout)鼠标移开事件,当鼠标移开当前对象时,执行onmouseout调用的程序。

    鼠标移开事件(onmouseout) 鼠标移开事件,当鼠标移开当前对象时,执行onmouseout调用的程序. 当把鼠标移动到"登录"按钮上,然后再移开时,触发onmouseout ...

  5. python库之sklearn

     一.安装sklearn conda install scikit-learn 参考文献 [1]整体介绍sklearn https://blog.csdn.net/u014248127/article ...

  6. sslforfree的证书合并成类似于certbot的ssl证书文件

    之前的證書都是通過 certbot的命令生成的,但是目前一個服務器太多個網站,太多個ssl證書,證書過期之後,目前是 通過 ssl for free 網站再生成新的 ssl證書,再次更新證書週期 Le ...

  7. 【JZOJ6375】华灵[蝶妄想]

    description analysis 明显括号序长度是偶数,如果其中一个是奇数,那么只能让这奇数行或列是括号序 对于两个都是偶数,需要分类讨论,假设\(n<m\) 有一种是牺牲掉\(n\ov ...

  8. thinkphp 系统流程

    用户URL请求 调用应用入口文件(通常是网站的index.php) 载入框架入口文件(ThinkPHP.php) 记录初始运行时间和内存开销 系统常量判断及定义 载入框架引导类(Think\Think ...

  9. STM32硬件错误HardFault_Handler的处理方法

    https://blog.csdn.net/electrocrazy/article/details/78173558

  10. Delphi 查找标题已知的窗口句柄,遍历窗口控件句柄

    有了回调函数的概念及上面的例子,我们可以继续了.其实想要找到一个标题已知的窗口句柄,用一个API函数就可以了:FindWindow.其函数原形是:function FindWindow(lpClass ...