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!
nginx 学习八 高级数据结构之基数树ngx_radix_tree_t的更多相关文章
- nginx学习六 高级数据结构之双向链表ngx_queue_t
1 ngx_queue_t简单介绍 ngx_queue_t是nginx提供的一个轻量级的双向链表容器,它不负责存储数据,既不提供数据的内存分配.它仅仅有两个指针负责把数据链入链表.它跟stl提供的qu ...
- nginx学习七 高级数据结构之动态数组ngx_array_t
1 ngx_array_t结构 ngx_array_t是nginx内部使用的数组结构.nginx的数组结构在存储上与大家认知的C语言内置的数组有相似性.比方实际上存储数据的区域也是一大块连续的内存. ...
- 菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t[转]
菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blo ...
- Linux 内核中的数据结构:基数树(radix tree)
转自:https://www.cnblogs.com/wuchanming/p/3824990.html 基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相 ...
- 一步一步分析Gin框架路由源码及radix tree基数树
Gin 简介 Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much ...
- [翻译]Linux 内核里的数据结构 —— 基数树
目录 Linux 内核里的数据结构 -- 基数树 基数树 Radix tree Linux内核基数树API 链接 Linux 内核里的数据结构 -- 基数树 基数树 Radix tree 正如你所知道 ...
- 数据结构(5) 第五天 快速排序、归并排序、堆排序、高级数据结构介绍:平衡二叉树、红黑树、B/B+树
01 上次课程回顾 希尔排序 又叫减少增量排序 increasement = increasement / 3 + 1 02 快速排序思想 思想: 分治法 + 挖坑填数 分治法: 大问题分解成各个小问 ...
- 数据库MySQL学习笔记高级篇
数据库MySQL学习笔记高级篇 写在前面 学习链接:数据库 MySQL 视频教程全集 1. mysql的架构介绍 mysql简介 概述 高级Mysql 完整的mysql优化需要很深的功底,大公司甚至有 ...
- 基数树与RCU锁
基数树是一种用空间换时间的数据结构,通过空间的冗余减少时间上的消耗.radix tree很适合稀疏的结构! 自从把RCU机制引入到基树中来,这里就有了个协议叫做:lockless的page-cache ...
随机推荐
- windows远程连接linux桌面---使用tightvnc或者tigervnc
一.安装tightvnc: tightvnc的安装在安装包中有详细的说明(README文件) 首先你要确保linux已经安装jpeg和zlib库, 2.编译 执行如下两个命令: [root@local ...
- UNIX系统文件
密码文件 密码文件又称用户数据库,一般为/etc/passwd,对应的结构为struct passwd,该文件内容大体如下: 描述 passwd字段 用户名 char* pw_name 加密密码 ch ...
- 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇03:子弹发射》
3.子弹发射 子弹发射概述: 在打飞机游戏中,子弹是自动发射的.子弹与子弹之间间隔一定的时间,玩家通过上下左右控制游戏角色,来达到躲避敌人及击中敌人的操作. 发射原理: 抽象理解为有两个容器存放子弹, ...
- 基础排序算法,java实现(快速,冒泡,选择,堆排序,插入)
1.冒泡排序: (1)比较相邻的元素.如果第一个比第二个大,就交换他们两个. (2)外面再套个循环就行. 算法复杂度:O(N2) 不罗嗦,上代码: //冒泡排序(两两交换,外加一个外循环) pub ...
- 删除 Mac OS X 中“打开方式”里重复或无用的程序列表
如果右键菜单的「打开方式」里出现了已不存在的应用程序或者重复的项目,打开终端,执行以下命令: /System/Library/Frameworks/CoreServices.framework/Ver ...
- 转:高性能Mysql主从架构的复制原理及配置详解
温习<高性能MySQL>的复制篇. 1 复制概述 Mysql内建的复制功能是构建大型,高性能应用程序的基础.将Mysql的数据分布到多个系统上去,这种分布的机制,是通过将Mysql的某一台 ...
- 设置sonar 界面为中文环境
sonar 默认是英文的界面 1.下载http://repository.codehaus.org/org/codehaus/sonar-plugins/l10n/sonar-l10n-zh-plug ...
- 【转】iPhone 6 屏幕揭秘
http://www.cocoachina.com/design/20141218/10680.html 一根线的渲染 为了说明多种设备的不同像素渲染情况,我们比较了一个一像素宽的线是怎样渲染的: 最 ...
- DelphiXE8怎么使用调试模式
需求:在开发Android程序时,大家一直是使用ShowMessage.其实XE是支持下断点的. 操作: 1.小米手机用USB线,连到电脑上. 2.小米手机-设置-关于手机-"MIUI版本& ...
- HDU 5810 Balls and Boxes (找规律)
Balls and Boxes 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5810 Description Mr. Chopsticks is i ...