Nginx源码初探之数据结构 - 基数树数据结构

基数树介绍

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

发布了27 篇原创文章 · 获赞 1 · 访问量 2238
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章