一、简介
C语言中的数组大小是固定的,每次扩容基本上都会引起数据的拷贝。nginx实现了一套数组基础组件,使用内存池方式分配空间,减少扩容引发的内存分配以及数据拷贝。
主要涉及ngx_array.h, ngx_array.c
二、 数据结构
typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;
- elts指向数组的首地址
- nelts表示当前数组中已有元素的个数
- size表示数组中每个元素的大小
- nalloc表示数组分配的空间总个数
- pool表示分配空间的内存池
2.1 逻辑结构图

2.2 实际结构图

三、相关API
3.1 创建数组
ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
    ngx_array_t *a;
    a = ngx_palloc(p, sizeof(ngx_array_t));
    if (a == NULL) {
        return NULL;
    }
    if (ngx_array_init(a, p, n, size) != NGX_OK) {
        return NULL;
    }
    return a;
}
static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    /*
     * set "array->nelts" before "array->elts", otherwise MSVC thinks
     * that "array->nelts" may be used without having been initialized
     */
    array->nelts = 0;
    array->size = size;
    array->nalloc = n;
    array->pool = pool;
    array->elts = ngx_palloc(pool, n * size);
    if (array->elts == NULL) {
        return NGX_ERROR;
    }
    return NGX_OK;
}
根据代码可以看出,所有的结构体以及数组本身空间都是从内存池中分配的。
3.2 销毁数组
void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;
    p = a->pool;
    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;
    }
}
对于数组的销毁,其实就是对内存池的操作,而只有两种情况才能立刻将内存返还给内存池,其他情况都只能等待内存池的销毁而回收内存。
数组分了两部分,数组头结构体和数组本身,只有数组结尾地址/结构体结尾地址是最后下次开始分配的地址,才能立刻将内存返还给内存池。
比如最开始画的那个图就是可以立刻回收的。
 回收后如下,原有的那些数据就变成了垃圾数据,并且这些内存可以立刻被重新分配给其他模块使用。
 
而如下的图就不能被回收,数组后面的空间已经被其他模块使用了,这样就不连续了,无法回收;而且这种内存结构情况下,如果是插入,并且写入数组元素已经达到上限,则只能重新分配空间,拷贝数据:
 
3.3 插入数据
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 = a->size * a->nalloc;
        p = a->pool;
        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;
}
和一般的插入函数不同,nginx的数组插入函数并没有传递需要插入的数据,这样提供的API更简单,不要考虑数据的类型,也就不需要考虑类似C++函数重载(或者模板)来实现不同类型参数的插入操作。nginx的数组插入函数只是返回需要插入数据的位置的首地址,需要调用方来写入数据。
当预分配的数组大小写满了,则需要扩容,这里分了两种情况:
- 数组结尾地址和下次分配地址重合,并且还有空间可以分配一个元素
 直接增加一个元素
- 数组结尾地址和下次分配地址不重合
 从内存池中重新分配2倍大小的新数组,将已有数据拷贝到新数组中,涉及数组拷贝
3.4 连续插入n个数据
void *
ngx_array_push_n(ngx_array_t *a, ngx_uint_t n)
{
    void        *elt, *new;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *p;
    size = n * a->size;
    if (a->nelts + n > a->nalloc) {
        /* the array is full */
        p = a->pool;
        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last
            && p->d.last + size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */
            p->d.last += size;
            a->nalloc += n;
        } else {
            /* allocate a new array */
            nalloc = 2 * ((n >= a->nalloc) ? n : a->nalloc);
            new = ngx_palloc(p, nalloc * a->size);
            if (new == NULL) {
                return NULL;
            }
            ngx_memcpy(new, a->elts, a->nelts * a->size);
            a->elts = new;
            a->nalloc = nalloc;
        }
    }
    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts += n;
    return elt;
}
和插入一个元素逻辑一致,只是在扩容2倍时,如果n大于等于数组原始大小,则扩大到2n个元素。
3.5 总结
nginx的数组api相对简单,就这4个api。但还是满满的料,很多细节可以慢慢品。









