ngx_queue_t不分配内存,只是将已分配好的内存用双向链表连接。
操作
// 有一个ngx_queue_t容器
原先:
消耗内存少,虽太适合超大规模数据的排序,但胜在简单使用。
作为C语言提供的通用双向链表,其设计思路值得参考。
在理解设计的时候可以将其想象成环形结构。
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s
{
ngx_queue_t* prev;
ngx_queue_t* next;
};
比较迷惑的是该容器与其中的元素使用相同的结构体。
操作
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q
(q)->prev = q; \
(q)->next = q
初始化容器
当容器一端(prev/next)不含有元素的时候,指向容器本身。
#define ngx_queue_empty(h) \
(h == (h)->prev)
(h == (h)->prev)
判断容器是否为空
如果容器与容器的prev相等,则容器不含任何元素
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
在容器h头部插入元素x:
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
在容器h尾部添加元素x
void ngx_queue_sort(ngx_queue_t *queue, ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *));
对容器进行排序,排序规则cmp要程序员自己实现。
#define ngx_queue_head(h) \
(h)->next
#define ngx_queue_last(h) \
(h)->prev
(h)->next
#define ngx_queue_last(h) \
(h)->prev
分别获取容器的头部元素和尾部元素。
第一眼看上去比较奇怪,取得头部的为何是(h)->next而不是(h)->prev呢?
看完下边的几个示意图就明白了。
// 有一个ngx_queue_t容器
ngx_queue_t gather;
// 使用ngx_queue_init初始化
ngx_queue_init(&gather);
// 容器中为空的时候情况:
// 容器中含有1个元素的情况:
// 在h头部插入x, ngx_queue_insert_head
原先:
h->prev = h;
h->next = h;
过程:
x->next = h; // h->next的值为h
h->prev = x; // x->next的值为h
x->prev = h;
h->next = x;
// 容器中含有2个元素的情况:
// 在h头部再插入y, ngx_queue_insert_head
原先:
h->prev = x;
x->prev = h;
h->next = x;
x->next = h;
#define ngx_queue_insert_head(h, y) \
(y)->next = (h)->next; \
(y)->next->prev = y; \
(y)->prev = h; \
(h)->next = y
(y)->next = (h)->next; \
(y)->next->prev = y; \
(y)->prev = h; \
(h)->next = y
过程:
y->next = x; // (h)->next的值为x
x->prev = y; // (y)->next的值为x
y->prev = h;
h->next = y;
// 容器中含有3个元素的情况:
// 在h尾部添加元素z, ngx_queue_insert_tail
原先:
h->next = y;
y->next = x;
x->next = h;
x->prev = y;
y->prev = h;
h->prev = x;
#define ngx_queue_insert_tail(h, z) \
(z)->prev = (h)->prev; \
(z)->prev->next = z; \
(z)->next = h; \
(h)->prev = z
(z)->prev = (h)->prev; \
(z)->prev->next = z; \
(z)->next = h; \
(h)->prev = z
过程:
z->prev = x;
x->next = z;
z->next = h;
h->prev = z;
通过以上示意图可以看到,容器的prev总指向容器的最后一个元素,next总指向容器的第一个元素。
仿佛该容器是一个圆环,头尾头尾头尾相互咬合。
使用:(示例来自于《深入理解Nginx: 模块开发与架构解析》)
ngx_queue_t可以作为另一个结构体的成员,然后该结构体对象通过该成员链接到链表上。
ngx_queue_t成员位置随意。
// 以下示例创建一个容器,然后添加5个元素,最后排序
struct TestNode
{
char* str;
ngx_queue_t qt;
int num;
};
// 排序函数,通过num排序
ngx_int_t cmp(const ngx_queue_t* a, const ngx_queue_t* b)
{
// 通过ngx_queue_data获取结构体TestNode对象的地址
TestNode* aNode = ngx_queue_data(a, TestNode, qt);
TestNode* bNode = ngx_queue_data(a, TestNode, qt);
return aNode->num > bNode->num;
}
// 遍历链表
void trav(ngx_queue_t* q)
{
ngx_queue_t* idx;
for(idx = ngx_queue_head(q);
idx != ngx_quque_sentinel(q);
idx = ngx_quque_next(idx))
{
TextNode* node = ngx_queue_data(idx, TestNode, qt);
printf("num: %d\n", node->num);
}
}
TestNode q;
TestNode nodes[5];
for(int i = 0; i < 5; ++i)
nodes[i].num = 5;
ngx_queue_insert_tail(&q, &nodes[0].qt);
ngx_queue_insert_head(&q, &nodes[1].qt);
ngx_queue_insert_tail(&q, &nodes[2].qt);
ngx_queue_insert_head(&q, &nodes[3].qt);
ngx_queue_insert_tail(&q, &nodes[4].qt);
// 遍历输出
trav(&q);
// 排序
ngx_queue_sort(&q, cmp);
// 遍历输出
trav(&q);