Ngx_queue_t结构:
Struct_ngx_queue_s{
ngx_queue_t *prev;//前驱
ngx_queue_t *next;//后继
};
思考1:该结构中为什么只有两个指针,没有数据域?它是如何存储和获得队列中节点的数据的?
通常意义上的双向链表是这个样子的:
Struct double_link_s{
int data;
double_link_s *prev;
double_link_s *next;
};
在操作过程中,对双向链表的操作基本都是围绕着prev和next展开的,和节点数据data关系不大,所以将双向链表的操作抽象出来,形成与链表节点无关的抽象可以帮助我们更好的去操作各种节点类型的双向链表;
这种链表的特点只有prev和next变量,没有表示链表节点的成员变量,因此,这种链表也被称为轻量级链表。
同时,由于这种链表没有节点成员变量,所以需要作为带有节点变量的结构体的成员变量存在,从而称这种链表为寄宿链表,链表所在结构体成为宿主。
相同的,ngx_queue_t就是轻量级链表。
思考2: 那么如何获取链表节点?
采用寄宿链表,如何根据链表获得结点数据,基本思路如下:
寄宿链表是链表节点结构体的一个成员变量,虽然结构体可能因为对齐问题使得各个成员变量在空间上不连续,但是整个结构体本身仍然可以看作是一块连续的内存区域。可以利用offsetof宏计算出寄宿链表成员变量相对于结构体起始位置的偏移量:
寄宿链表的起始地址-寄宿链表成员变量相对于结构体起始位置的偏移量 = 结构体的起始地址
#define ngx_queue_data(q,type,link)//q为寄宿链表变量,type是寄宿链表所在结构体的类型,link是寄宿链表在type结构体中的变量名。
(type*)((u_char*)q-offsetof(type,link))//返回宿主结构体的首地址
队列的相关操作:
ngx_queue_init();
ngx_queue_empty();
ngx_queue_insert_head();
ngx_queue_insert_tail();
ngx_queue_remove();
ngx_queue_split();
ngx_queue_add();
ngx_queue_t*ngx_queue_middle();
ngx_queue_sort();
(1)ngx_queue_init();//初始化
//初始状态的链表 prev= next = head;
(2)ngx_queue_empty();//判空
(3)头插-ngx_queue_insert_head();
#definengx_queue_insert_head(h,x)//h为链表指针,x为插入的元素
(X)->next = (h)->next;
(x)->next->prev = (x);
(x)->prev = (h);
(h)->next = (x);
(4)尾插ngx_queue_insert_tail();
#definengx_queue_insert_tail(h,x)//h->prev为最后的一个节点
(x)->prev = (h)->prev;
(x)->prev->next = (x);
(x)->next = (h);
(h)->prev = (x);
(5) 链表删除-ngx_queue_remove();
基本思路:x为要删除的结点,将x下一个结点的prev指针指向x的上一个结点,再将x的前一个结点的next指针指向x的下一个节点,不用处理内存释放
#define ngx_queue_remove()
(x)->next->prev = (x)->prev;
(x)->prev->next = (x)->next;
(6) 链表的拆分--ngx_queue_split()
//h为链表容器,q为链表h中的一个元素,可以将链表h以元素q为界拆分为两个链表h和n(n为一个新的空节点),h由原链表的前半部分组成(不包含q),n由后半部分组成,q为首元素。
基本步骤:更改h的prev和h->prev->next;n的prev和next以及n->prev->next;更改p->prev
#define ngx_queue_split(h,q,n)
(n)->prev = (h)->prev;
(n)->next = q;
(n)->prev->next = n;
(h)->prev = (q)->prev;
(h)->prev->next = h;
(q)->prev = n;
(7)链表的合并-ngx_queue_add();
#define_ngx_queue_add(h,n)
(h)->prev->next = (n)->next;
(n)->next->prev = (h)->prev;
(h)->prev = (n)->prev;
(h)->prev->next = h;
(8)链表中心元素--ngx_queue_t *ngx_queue_middle();
ngx_queue_t*ngx_queue_middle(ngx_queue_t *queue)
(9)链表排序--ngx_queue_sort()
队列链表排序采用的是插入排序方法(从后向前找到第一个比tmp小的,寻找的同时挪动位置)即从第一个结点遍历开始,依次将当前节点(q)插入前面已经排好序的队列中。
总结:
Nginx中实现的queue有两个最重要的特点:
(1)轻量级:队列节点没有数据区,这是为了更好的抽象队列这个基础数据结构,ngx_queue_t这结构体是被封装到其他结构体中,这样来实现队列的功能;
(2)实际上队列实现的是一个循环队列,其中有一个哨兵结点来连接首尾节点,只是一个首尾节点的连接器
Ngx_queue优势和特点
Ngx_queue作为顺序容器链表,优势在于其可以高效执行插入,删除等操作,并且在此过程中只需要修改指针指向,而不需要对数据进行操作,对于频繁修改的容器很适合,相对于STL list 具备以下特点:
(1)自身实现了排序功能;(2)轻量级,不需要负责内存的分配(只是将分配好的内存用双向链表连接,消耗内存少)(3)自身支持两个链表的合并。