nginx 學習八 高級數據結構之基數樹ngx_radix_tree_t

1 nginx的基數樹簡介

基數樹是一種二叉查找樹,它具備二叉查找樹的所有優點:檢索、插入、刪除節點速度快,支持範圍查找,支持遍歷等。在nginx中僅geo模塊使用了基數樹。nginx的基數樹使用ngx_radix_tree_t這個結構體表示的。ngx_radix_tree_t要求存儲的每個節點都必須以32位整形作爲區別任意兩個節點的唯一標識。ngx_radix_tree_t基數樹會負責分配每個節點佔用的內存,基數樹的每個節點中可存儲的值只是一個指針,這個指針指向實際的數據。

節點結構ngx_radix_node_t:

typedef struct ngx_radix_node_s  ngx_radix_node_t;
//基數樹的節點
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:

typedef struct {
    ngx_radix_node_t  *root;//根節點
    ngx_pool_t        *pool;//內存池,負責分配內存
    ngx_radix_node_t  *free;//回收釋放的節點,在添加新節點時,會首先查看free中是否有空閒可用的節點
    char              *start;//已分配內存中還未使用內存的首地址
    size_t             size;//已分配內存內中還未使用內存的大小
} ngx_radix_tree_t;
這裏要注意free這個成員,它用來回收刪除基數樹上的節點,並這些節點連接成一個空閒節點鏈表,當要插入新節點時,首先查看這個鏈表是否有空閒節點,如果有就不申請節點空間,就從上面取下一個節點。

2ngingx基數的基本操作函數

ngx_radix_tree_t基本操作函數如下:

//創建基數樹,preallocate是預分配節點的個數
ngx_radix_tree_t *ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate);

//根據key值和掩碼向基數樹中插入value,返回值可能是NGX_OK,NGX_ERROR, NGX_BUSY
ngx_int_t ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask, uintptr_t value);

//根據key值和掩碼刪除節點(value的值)
ngx_int_t ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask);

//根據key值在基數樹中查找返回value數據
uintptr_t ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key);


2.1 ngx_radix_tree_create創建基數樹

ngx_radix_tree_create會構造一個基數樹。這個函數會根據第二個參數來判斷是否預先創建一棵空的基數樹:

1)如果preallocate爲0,只申請ngx_radix_tree_t這個結構體,並返回

2)如果preallocate爲-1,會根據ngx_pagesize/sizeof(ngx_radix_tree_t)的情況來構造一棵深度爲7(或者8)的沒有存儲數據的基數樹。

源代碼:

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));//申請ngx_raidx_tree_t,這個tree是返回的指針
    if (tree == NULL) {
        return NULL;
    }
    //初始化ngx_radix_tree_t本身
    tree->pool = pool;
    tree->free = NULL;
    tree->start = NULL;
    tree->size = 0;

    tree->root = ngx_radix_alloc(tree);//申請一個基數節點
    if (tree->root == NULL) {
        return NULL;
    }
	//初始化root節點
    tree->root->right = NULL;
    tree->root->left = NULL;
    tree->root->parent = NULL;
    tree->root->value = NGX_RADIX_NO_VALUE;

	/*prealloc=0時,只創建結構體ngx_radix_tree_t,沒有創建任何基數樹節點*/
    if (preallocate == 0) {
        return tree;
    }
	/*prealloc=-1時,根據下面的情況創建基數樹節點*/
    if (preallocate == -1) {
        switch (ngx_pagesize / sizeof(ngx_radix_tree_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;
    //加入preallocate=7,最終建的基數樹的節點總個數爲2^(preallocate+1)-1,每一層個數爲2^(7-preallocate)
    //循環如下:
    //preallocate  =      7         6        5         4         3         2        1
    //mask(最左8位)=      10000000  11000000 11100000  11110000  11111000  11111100 11111110
    //inc          =     10000000  01000000 00100000  00010000  00001000  00000100 00000010
    //增加節點個數    =      2         4        8         16        32        64       128
    while (preallocate--) {

        key = 0;
        mask >>= 1;
        mask |= 0x80000000;

        do {//根據inc的值添加節點
            if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
                != NGX_OK)
            {
                return NULL;
            }

            key += inc;//當preallocate=0時,是最後一層,構建的節點個數爲2^preallocate

        } while (key);

        inc >>= 1;
    }

    return tree;
}

2.2 ngx_radix32tree_insert向基數樹中插入樹節點

nginx的基數樹只處理key值爲整形的情況,所以每個整形被轉化爲二進制數,並且樹的最大深度是32層。根據二進制位數從左到右,如果當前位爲1,就向右子樹,否則向左子樹插入。當然有時候我們不想構建深度爲32的基數樹,nginx爲此提供了一個掩碼mask,這個掩碼中1的個數決定了基數樹的深度。

代碼:

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;//從最左位開始,判斷key值

    node = tree->root;
    next = tree->root;

    while (bit & mask) {
        if (key & bit) {//等於1向右查找
            next = node->right;

        } else {//等於0向左查找
            next = node->left;
        }

        if (next == NULL) {
            break;
        }

        bit >>= 1;
        node = next;
    }

    if (next) {//如果next不爲空
        if (node->value != NGX_RADIX_NO_VALUE) {//如果數據不爲空
            return NGX_BUSY;//返回NGX_BUSY
        }

        node->value = value;//直接賦值
        return NGX_OK;
    }

    //如果next爲中間節點,且爲空,繼續查找且申請路徑上爲空的節點
    //比如找key=1000111,在找到10001時next爲空,那要就要申請三個節點分別存10001,100011,1000111,
    //1000111最後一個節點爲key要插入的節點
    while (bit & 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;
        node = next;
    }

    node->value = value;//指向數據區

    return NGX_OK;
}
2.3ngx_radix32tree_delete刪除節點

刪除一個節點和插入節點的操作幾乎一樣,不過要注意兩點:

1)如果刪除的是葉子節點,直接從基數樹中刪除,並把這個節點放入free鏈表

2)如果不是葉子節點,把value值置爲NGX_RADIX_NO_VALUE

代碼:

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;
    //根據key和掩碼查找
    while (node && (bit & mask)) {
        if (key & bit) {
            node = node->right;

        } else {
            node = node->left;
        }

        bit >>= 1;
    }

    if (node == NULL) {//沒有找到
        return NGX_ERROR;
    }

	//node不爲葉節點直接把value置爲空
    if (node->right || node->left) {
        if (node->value != NGX_RADIX_NO_VALUE) {//value不爲空
            node->value = NGX_RADIX_NO_VALUE;//置空value
            return NGX_OK;
        }

        return NGX_ERROR;//value爲空,返回error
    }

	//node爲葉子節點,直接放到free區域
    for ( ;; ) {//刪除葉子節點
        if (node->parent->right == node) {
            node->parent->right = NULL;//

        } else {
            node->parent->left = NULL;
        }

		//把node鏈入free鏈表
        node->right = tree->free;//放到free區域
        tree->free = node;//free指向node
        //假如刪除node以後,父節點是葉子節點,就繼續刪除父節點,
		//一直到node不是葉子節點
        node = node->parent;

        if (node->right || node->left) {//node不爲葉子節點
            break;
        }

        if (node->value != NGX_RADIX_NO_VALUE) {//node的value不爲空
            break;
        }

        if (node->parent == NULL) {//node的parent爲空
            break;
        }
    }

    return NGX_OK;
}

2.4ngx_radix32tree_find查找節點返回數據

這個函數是這四個函數中最簡單的一個,就是根據key值查詢,如果找到返回value值,沒有找到返回NGX_RADIX_NO_VALUE。

代碼:

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;
}

2.5ngx_radix_alloc申請節點函數

ngx_radix_alloc爲基數樹申請節點:

1)如果free鏈表不爲空,直接從上面取下一個空閒節點

2)free鏈表爲空,則申請一個節點

代碼:

static void *
ngx_radix_alloc(ngx_radix_tree_t *tree)
{
    char  *p;

    if (tree->free) {//如果free中有可利用的空間節點
        p = (char *) tree->free;//指向第一個可利用的空間節點
        tree->free = tree->free->right;//修改free
        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 = tree->start;
    tree->start += sizeof(ngx_radix_node_t);
    tree->size -= sizeof(ngx_radix_node_t);

    return p;
}
3測試例子

下面是一個測試ngx_radix_tree_t的例子,在寫完這個測試列子運行的時候,出現“核心錯誤(存儲已轉移)”,先按老辦法調試--直接打印定位錯誤代碼範圍,找過了錯誤是在這個函數裏面:ngx_radix32tree_create,尼碼,源代碼有錯誤,蒙了,不知到怎麼調試下去,因爲以前沒在linux跟蹤代碼執行,沒法了,直接搜資料學gdb調試程序,單步跟蹤調試了整整一個晚上,發現在下面這句代碼中出錯:

tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
gdb  p ngx_pagesize 竟然是0,暈了很久找到這個ngx_pagesize的定義,是個全局變量沒有初始化。

誰能知道系統中的全局變量在哪裏用,在哪裏初始化?

http://blog.csdn.net/xiaoliangsky/article/details/39695591

測試代碼:

/********************************************************
author: smtl
date: 2014-10-01--4:50
*********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <ngx_core.h>
#include <ngx_config.h>
#include <ngx_conf_file.h>
#include <ngx_palloc.h>
#include <ngx_radix_tree.h>

////////////////////////////////////////////////////////////////////////////////////
//不加下面這兩個定義編譯會出錯
volatile ngx_cycle_t  *ngx_cycle;

void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
            const char *fmt, ...)
{
}
////////////////////////////////////////////////////////////////////////////////////

//先序遍歷radix_tree
void travel_radix_tree(ngx_radix_node_t *root);

int main()
{
	/*基數樹節點的數據集:ngx_int_t類型,只是測試,實際上可以爲任何類型*/
	ngx_int_t data[64];
	ngx_int_t i = 0;
	for (i = 0; i < 64; ++i)
	{
		data[i] = rand()%10000;
	}

	/*創建內存池對象*/
	ngx_pool_t* pool = ngx_create_pool(1024, NULL);
	if (pool == NULL)
	{
		printf("create pool failed!\n");
		exit(1);
	}
	
    //printf("xxxxx\n");
	////////////////////////////////////////////////////////////////////////////////
	//一定要初始化ngx_pagesize這個全局變量,調試了一個晚上
	//不初始化,會出現段錯誤(核心已轉儲),這也是全局變量的潛在危害:
	//你不知道你的程序中是否用到這個全局變量,如果用到,這個全局變量初始化了沒有
	//在一些大的程序中,你根本無法快速知道這些,所以應儘量避免使用全局變量
    ngx_pagesize = getpagesize();
	printf("pagesize = %d\n", ngx_pagesize);
	////////////////////////////////////////////////////////////////////////////////

    /*創建基數樹,prealloc=0時,只創建結構體ngx_radix_tree_t,沒有創建任何基數樹節點*/
	ngx_radix_tree_t *tree = ngx_radix_tree_create(pool, -1);
	//printf("xxxxxY\n");
	if (tree == NULL)
	{
		printf("crate radix tree failed!\n");
		exit(1);
	}
	
	
	/*插入data*/
	ngx_uint_t deep = 5;//樹的最大深度爲4
	ngx_uint_t mask = 0;
	ngx_uint_t inc  = 0x80000000;
	ngx_uint_t key  = 0;
	ngx_uint_t cunt = 0;//data數組的索引

	while (deep--)
	{
		key    = 0;
		mask >>= 1;
		mask  |= 0x80000000;
		do 
		{
			if (NGX_OK != ngx_radix32tree_insert(tree, key, mask, &data[cunt]))
			{
				printf("insert error\n");
				exit(1);
			}

			key += inc;

			++cunt;
			if (cunt > 63)
			{
				cunt = 63;
			}
		}while(key);

		inc >>= 1;
	}

	/*先序打印數據*/
	travel_radix_tree(tree->root);
	printf("\n");

	/*查找測試*/
	ngx_uint_t tkey  = 0x58000000;
	ngx_int_t* value = ngx_radix32tree_find(tree, 0x58000000);
	if (value != NGX_RADIX_NO_VALUE)
	{
		printf("find the value: %d with the key = %x\n", *value, tkey);
	}
	else
	{
		printf("not find the the value with the key = %x\n", tkey);
	}
    
	/*刪除測試*/
	if (NGX_OK == ngx_radix32tree_delete(tree, tkey, mask))
	{
		printf("delete the the value with the key = %x is succeed\n", tkey);
	}
	else
	{
		printf("delete the the value with the key = %x is failed\n", tkey);
	}

    value = ngx_radix32tree_find(tree, 0x58000000);
	if (value != NGX_RADIX_NO_VALUE)
	{
		printf("find the value: %d with the key = %x\n", *value, tkey);
	}
	else
	{
		printf("not find the the value with the key = %x\n", tkey);
	}

	return 0;
}

void travel_radix_tree(ngx_radix_node_t *root)
{
	if (root->left != NULL)
	{
		travel_radix_tree(root->left);
	}
	
	if (root->value != NGX_RADIX_NO_VALUE)
	{
		ngx_int_t* value = root->value;
		printf("%d\n", *value);
	}

	if (root->right != NULL)
	{
		travel_radix_tree(root->right);
	}
}


work  hard!


http://blog.csdn.net/xiaoliangsky/article/details/39695591

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章