数组和链表的优缺点我们都知道,nginx的数组链表结合了这两种数据结构的优点,表面是链表,链表里每个节点是一个固定大小的数组。结构如下图。
ngx_list.h
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_LIST_H_INCLUDED_
#define _NGX_LIST_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
typedef struct ngx_list_part_s ngx_list_part_t;
/*
ngx_list是一个数组链表,表面是一个链表,然后链表里的每一个节点都是一个固定大小的数组,
ngx_list_t是整个结构的领导者,他指向ngx_list_part_s,ngx_list_part_s指向数组的存储空间,
因为链表中每个节点,对应的数组可以存储多少个元素,每个元素的大小是固定的,所以这些元数据是存在ngx_list_s中,
没必要每个节点都存一份。ngx_list_part_s就是这个链表中的节点结构体,
*/
struct ngx_list_part_s {
void *elts; // 数组首地址
ngx_uint_t nelts; // 数组已经存储了多少个元素
ngx_list_part_t *next; // 指向下一个节点的指针
};
typedef struct {
/*
最后一个数组节点的结构体指针,这个指针指向的节点是当前也是唯一一个可以用来存储数据的节点,
每次需要存储数据的时候,都是从last指针指向的节点开始找的,如果存满了就继续增加节点,此时last指向新的
节点,last一直指向当前可以存储数据的数据节点。
*/
ngx_list_part_t *last;
ngx_list_part_t part; // 第一个数组节点的结构体
size_t size; // 数组中,每个元素的大小
ngx_uint_t nalloc; // 每个数组节点可以存储多少个元素
ngx_pool_t *pool; // 用来分配内存的pool
} ngx_list_t;
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
/*
初始化数组链表函数
@param list需要初始化的链表结构体
@param pool 用于分配内存的pool
@param n 该数组分配共可以存储几个元素
@param size 该数组中每个元素的大小
@return NGX_OK | NGX_ERROR
*/
static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
// 分配一个n*size大小的数组。并用赋值给第一个链表节点的list->part.elts,指向数组的首地址
list->part.elts = ngx_palloc(pool, n * size);
if (list->part.elts == NULL) {
return NGX_ERROR;
}
// 数组已存0个元素
list->part.nelts = 0;
// 下一个链表节点
list->part.next = NULL;
// 此时,第一个节点是最后一个节点,需要取地址
list->last = &list->part;
// 每个链表节点中,每个数组元素的大小
list->size = size;
// 每个链表节点中,数组最多可以存储多少个元素
list->nalloc = n;
list->pool = pool;
return NGX_OK;
}
/*
*
* the iteration through the list:
*
* part = &list.part;
* data = part->elts;
*
* for (i = 0 ;; i++) {
*
* if (i >= part->nelts) {
* if (part->next == NULL) {
* break;
* }
*
* part = part->next;
* data = part->elts;
* i = 0;
* }
*
* ... data[i] ...
*
* }
*/
void *ngx_list_push(ngx_list_t *list);
#endif /* _NGX_LIST_H_INCLUDED_ */
ngx_list.c
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
/*
创建数组链表函数
@param pool 用于分配内存的pool
@param n 该数组分配共可以存储几个元素
@param size 该数组中每个元素的大小
@return 指向数组链表的结构体指针 ngx_list_t
*/
ngx_list_t * ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
ngx_list_t *list;
// 分配一个ngx_list_t结构体大小的空间,用来存储数组链表的一些信息
list = ngx_palloc(pool, sizeof(ngx_list_t));
if (list == NULL) {
return NULL;
}
// 分配n * size大小的数组,并初始化第一个数组节点,使得第一个数组节点的指针指向分配的内存
list->part.elts = ngx_palloc(pool, n * size);
if (list->part.elts == NULL) {
return NULL;
}
// 同ngx_list_init函数
list->part.nelts = 0;
list->part.next = NULL;
list->last = &list->part;
list->size = size;
list->nalloc = n;
list->pool = pool;
return list;
}
/*
返回可以用来存储数据的首地址,如果第一个数组节点没有空间就往后面的节点找,如果都没有空间就动态增加数组节点
@param l 存储数据的链表
@return 可以用来存储数据的首地址
*/
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管理
last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
if (last->elts == NULL) {
return NULL;
}
// 新开辟的数组存储了0个元素
last->nelts = 0;
// 该数组节点是最后一个数组节点,next为NULL
last->next = NULL;
// 把新开辟的数组节点插入到链表的最后面
l->last->next = last;
// 此时新开辟的数组节点是最后一个节点,也是唯一一个可用节点,可用的意思是还可以用来存储元素
l->last = last;
}
// 不管需不需要新增数组节点,last->elts + l->size * last->nelts此时都指向第一个可用的地址
elt = (char *) last->elts + l->size * last->nelts;
// 存储的元素个数加1
last->nelts++;
// 返回可存储数据的首地址,nginx里都是返回一个地址,然后在push函数外进行数据存储,而不是在push函数里操作
return elt;
}