應該說大家對這兩個數據結構相當熟悉了,因此我們一並將它們進行分析,瞧一瞧nginx是如何實現它們的。在此篇之前,我們已經對nginx 內存池(pool)進行了分析,在此基礎上來理解ngnix對它們的實現將變得非常簡單,特別是內存池(pool)中的ngx_palloc 函數在這兩個結構中多次用到,若不清楚想了解原理的可以看看我前面寫的文章,它返回的是在內存池分配好空間了的首地址。
一、ngx_array 數組:
struct ngx_array_s { void *elts; ngx_uint_t nelts; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; };
參數說明:elts為array數組中元素的首地址,nelts數組中已分配的元素個數,size每個元素大小,nalloc數組容量,pool其所在的內存池。
能夠支持五種函數操作:
創建數組:
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
數組初始化:
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
數組注銷:
ngx_array_destroy(ngx_array_t *a);
添加一個數組元素:
ngx_array_push(ngx_array_t *a);
添加n個數組元素:
ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
ngx_array_create和ngx_array_init,代碼比較簡明就不多說了,值得注意的是兩者之間的差別,ngx_array_init使用情形是已經存在了ngx_array_t的結構體,而ngx_array_create則從零開始建起,貼出代碼:

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; } 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; } a->elts = ngx_palloc(p, n * size); if (a->elts == NULL) { return NULL; } a->nelts = 0; a->size = size; a->nalloc = n; a->pool = p; return a; }
重點介紹下ngx_array_push函數
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; }
代碼里面主要就是if和else的邏輯關系,可解釋為以下幾種情形:
第一,如果array當前已分配的元素個數小於最大分配個數,那么用數組元素首地址a->elts 計算出分配元素的首地址,並返回結果。
第二,如果array中當前已分配元素個數等於最大分配元素個數,並且array所在內存池pool還有空間可分配給新元素,那么對array在本對array進行擴充一個單元,擴充后即變成第一中情形進行處理。
第三,如果array中當前已分配元素個數等於最大分配元素個數,並且array所在內存池pool沒有空間可分配給新元素,那么對array大小增大一倍后進行重新分配,並將原來array內容拷貝到新地址空間中,完成后最大容量變成原來的兩倍,同第一中情形進行處理。
同樣的ngx_array_push_n,也是類似的處理,不再次重復了。ngx_array_destroy數組注銷函數,則是對pool池中數據分配段末指針,移動array中允許存儲的元素總共需要的空間大小的距離即可實現注銷工作。對於此處不多加說明,如有不明白之處請參考前面已寫的關於nginx內存池(pool)的分析就很容易明白了。附上代碼:

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; } }
二、ngx_list 鏈表:
理解完ngx_array后,再來看ngx_list 就會發現它們之間有許多類似的地方。首先還是看其中的兩個數據結構。
struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; };
參數說明:elts指向鏈表元素地址,nelts為鏈表中含有元素的個數,next下一個元素地址。
typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
參數說明:這個結構體是用來管理存在的鏈表的。last為鏈表的首地址。part一個管理鏈表元素的鏈表結構,size每個元素大小,nalloc鏈表允許的最多元素個數,pool是所在的內存池。
如果對參數說明中有疑惑的地方,暫且可以放過繼續閱讀,讀到后文中的圖1時,再回頭過來看就應該能夠明白了。同樣的在ngx_list中存在着ngx_list_create和ngx_list_init ,其類似於前面的array,也是注意下需要使用范圍,代碼簡明不多介紹,唯一注意一點的是list->last = &list->part;它即表示第一個鏈表的頭節點即為本身參數中的part,附上源碼:

static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size) { list->part.elts = ngx_palloc(pool, n * size); if (list->part.elts == NULL) { return NGX_ERROR; } list->part.nelts = 0; list->part.next = NULL; list->last = &list->part; list->size = size; list->nalloc = n; list->pool = pool; return NGX_OK; } 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; } list->part.elts = ngx_palloc(pool, n * size); if (list->part.elts == NULL) { return NULL; } list->part.nelts = 0; list->part.next = NULL; list->last = &list->part; list->size = size; list->nalloc = n; list->pool = pool; return list; }
另外一個函數ngx_list_push與array中的同樣相似。代碼如下:

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; }
一個主要的if解釋如下,如果最后一個鏈表中的元素個數達到了最大,那么就需要擴充管理鏈表的鏈表;如果沒有達到最大就正常分配節點單元。如圖示1,便於理解
圖1 ngx_list 結構示意圖
我們可以從圖中看出,ngx_list 是一種鏈式存儲和順序儲存相結合的鏈表結構,並不是像傳統的鏈表結構。