您的位置:首页 > 运维架构 > Nginx

Nginx基础. Nginx数组与链表

2015-08-29 19:43 501 查看
ngx_array_t

实现文件: ./src/core/ngx_array.h ./src/core/ngx_array.c

1. 数据结构定义

typedef struct {
void        *elts;          //数组数据区起始位置
ngx_uint_t   nelts;         //当前存放元素个数
size_t       size;          //存放的每个元素的大小
ngx_uint_t   nalloc;        //数组最大能存放的元素个数
ngx_pool_t  *pool;          //使用的内存池对象
} ngx_array_t;
单纯的从结构定义可以看出, 数组是与内存池绑定的, 占用的空间由内存池分配

2. 数组操作

1.)创建
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
2.) 初始化
static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);
3.) 销毁
void ngx_array_destroy(ngx_array_t *a);
4.) 加入元素
void *ngx_array_push(ngx_array_t *a);
5.) 加入n个相同元素
void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);


3. 操作详解

关于数组的操作, 大多简单. 只拿出几个谈谈

先看的当然是数组的创建函数, 但是它就是简单的内存分配以及变量初始化, 这里就不贴出来了

下面是数组的销毁动作, 主要就是修改了内存池的last指针, 并没有调用free等释放内存的操作,显然,这种维护效率是很高的。

void
ngx_array_destroy(ngx_array_t *a)
{
ngx_pool_t  *p;

p = a->pool;

//下面是对数组内容和其结构的"删除", 实际上只是指针的移动. 如果正巧他们不是正好在last指针前面, 那么也就不管了, 反正内存是统一管理的
if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
p->d.last -= a->size * a->nalloc;
}

if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
p->d.last = (u_char *) a;
}
}


最后, 看看如何添加一个数组元素

根据之前学习STL的经验, 如果当前元素超过了最大元素个数, 那么就会重新分配一段内存给这个数组

void *
ngx_array_push(ngx_array_t *a)
{
void        *elt, *new;
size_t       size;
ngx_pool_t  *p;

//如果这段数组内存还可以存的下新的元素, 那么处理就简单了
if (a->nelts == a->nalloc) {

/* the array is full */
//首先size得到原来数组的大小
size = a->size * a->nalloc;

p = a->pool;

//判断last指针是否正好在这段数组内存的后面, 如果正好, 那么我们继续判断是否接下来还有空间可以存数组元素
//这里就跟STL里的vector不同了. 因为我们是在内存池里操作, 如果发现这段数组内存后面正好还有空余的, 那么就直接利用把, 省的再繁杂的操作
if ((u_char *) a->elts + size == p->d.last
&& p->d.last + a->size <= p->d.end)
{
/*
* the array allocation is the last in the pool
* and there is space for new allocation
*/

p->d.last += a->size;
//因为这里是在原有数组已满的基础上添加元素,又正好有这段内存, 所以需要修改最大元素存储的数量
a->nalloc++;

} else {
/* allocate a new array */
//否则的话, 只能老老实实的分配一段新的内存, 且大小是原来的两倍
new = ngx_palloc(p, 2 * size);
if (new == NULL) {
return NULL;
}
//将原来的元素都复制过来
ngx_memcpy(new, a->elts, size);
a->elts = new;
a->nalloc *= 2;
}
}
//将新元素需要的内存返回
elt = (u_char *) a->elts + a->size * a->nelts;
a->nelts++;

return elt;
}


ngx_list_t

链表是Nginx封装的链表容器, 它在Nginx中使用的非常频繁.

相比较libevent中的TAILQ队列, Nginx中的list理解起来要容易的多

下面就简单的认识一下这个链表.

1. 数据结构的定义

typedef struct ngx_list_part_s ngx_list_part_t;

struct ngx_list_part_s {
void             *elts;           //数组的起始地址
ngx_uint_t        nelts;          //当前存放的元素个数
ngx_list_part_t  *next;
};

typedef struct {
ngx_list_part_t  *last;           //指向链表最后一个节点
ngx_list_part_t   part;           //链表头中包含的第一个节点(part)
size_t            size;           //虽然存储的类型是任意的, 但还是要通过size来限制每个元素占用的空间大小, 即数组中元素大小必须小于或等于size
ngx_uint_t        nalloc;         //链表的数组元素一旦分配后是不可更改的. nalloc表示每个数组容量
ngx_pool_t       *pool;
} ngx_list_t;
ngx_list_t描述整个链表, 而ngx_list_part_t只描述链表的一个元素.

要注意的是, ngx_list_t不是一个单纯的链表, 它是存储数组的链表, 即每个链表元素ngx_list_part_t是一个数组, 拥有连续的内存

该数组既依赖ngx_list_t结构中的size和nalloc来表示数组的容量, 同时又依靠每个ngx_list_part_t成员中的nelts来表示当前已经使用了多少容量.

对于这样的设计, 有什么好处呢?

1. 链表中存储的元素是灵活的, 可以是任何一种数据结构

2. 链表元素需要占用的内存由ngx_list_t管理, 它已经通过数组分配好了

3. 小块的内存使用链表访问是低效率的, 使用数组偏移来访问则高效的多

2. 链表的操作

ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
ngx_list_t  *list;

list = ngx_palloc(pool, sizeof(ngx_list_t));
if (list == NULL) {
return NULL;
}

if (ngx_list_init(list, pool, n, size) != NGX_OK) {
return NULL;
}

return list;
}
在ngx_list_init中, 不仅初始化了一些变量, 还分配了 n*size 大小内存的一个数组. 说明最初创建的list会自带一个数组元素

除了初始化, 还有一个要看的就是插入元素了

既然链表的每个元素是数组, 那么新值插入的位置就是最后一个数组元素的末尾, 或是另开一个链表元素来装这个值

void *
ngx_list_push(ngx_list_t *l)
{
void             *elt;
ngx_list_part_t  *last;

last = l->last;

//如果我们要插入的那个数组已经满了
if (last->nelts == l->nalloc) {

/* the last part is full, allocate a new list part */

//继续分配
last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
if (last == NULL) {
return NULL;
}

last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
if (last->elts == NULL) {
return NULL;
}

last->nelts = 0;
last->next = NULL;
//新的数组元素放在链表的末尾
l->last->next = last;
l->last = last;
}
//返回新值要插入的空间起始地址
elt = (char *) last->elts + l->size * last->nelts;
last->nelts++;

return elt;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: