您的位置:首页 > 理论基础 > 数据结构算法

nginx源码初读(7)--让烦恼从数据结构开始(ngx_queue)

2016-02-23 19:17 585 查看
ngx_queue_t是nginx中的双向链表,我们看看它的数据结构,太简单:

typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t  *prev;
ngx_queue_t  *next;
};


是不是很简单?学过数据结构的我们一看就懂了。唉?等一下!等一下!等一下~~我老公呢~等一下。。(乱入,什么东西- -)

等一下,我们发现它少了我们必须用到的数据存储啊,我们链表里的数据放哪儿去了。刚开始我也很疑惑,后来看到它用得地方瞬间懂了,不只是懂了,瞬间惊呆了!!

它是这么用的:

ngx_queue_t free;            // 这是一个小哨兵,不存数据
ngx_queue_init(&free);       // 初始化一下下
struct node{
int data;
ngx_queue_t queue;       // 把queue这个结构体乖乖的放在我们要创建链表的结构里
}  node1;
gx_queue_insert_head(&free, &node1.queue);
// 对!就这么用,先把queue放任意结构体里,再连到free这个哨兵上,都可以做链表用啦,写这个的人真是绝了


好了,知道它是怎么用的了,我们能放下好奇心好好看看代码了。

#define ngx_queue_init(q)                                                     \
(q)->prev = q;                                                            \
(q)->next = q
/* 初始化函数,哨兵的前后都是自己,也就是链表是空的,没有数据节点 */

#define ngx_queue_empty(h)    \
(h == (h)->prev)
/* 判断链表是不是空的 */

#define ngx_queue_insert_head(h, x)                                           \
(x)->next = (h)->next;                                                    \
(x)->next->prev = x;                                                      \
(x)->prev = h;                                                            \
(h)->next = x
/* 插入是直接插入头部的 */

#define ngx_queue_insert_after   ngx_queue_insert_head
/* 插入到某节点的后面,跟insert_head是一样的 */

#define ngx_queue_insert_tail(h, x)                                           \
(x)->prev = (h)->prev;                                                    \
(x)->prev->next = x;                                                      \
(x)->next = h;                                                            \
(h)->prev = x
/* 因为是双向循环链表,头尾相连,所以插入到尾部也很容易,就是头部的前一个元素 */

#define ngx_queue_head(h)                                                     \
(h)->next
#define ngx_queue_last(h)                                                     \
(h)->prev
/* 通过哨兵获得头尾元素 */

#define ngx_queue_sentinel(h)                                                 \
(h)
#define ngx_queue_next(q)                                                     \
(q)->next
#define ngx_queue_prev(q)                                                     \
(q)->prev
/* 返回自身或者前后的元素 */

#if (NGX_DEBUG)
#define ngx_queue_remove(x)                                                   \
(x)->next->prev = (x)->prev;                                              \
(x)->prev->next = (x)->next;                                              \
(x)->prev = NULL;                                                         \
(x)->next = NULL
#else
#define ngx_queue_remove(x)                                                   \
(x)->next->prev = (x)->prev;                                              \
(x)->prev->next = (x)->next
#endif
/* 把节点从链表中删除的操作,在debug模式下还会重置指针为null */

#define ngx_queue_split(h, q, n)                                              \
(n)->prev = (h)->prev;                                                    \
(n)->prev->next = n;                                                      \
(n)->next = q;                                                            \
(h)->prev = (q)->prev;                                                    \
(h)->prev->next = h;                                                      \
(q)->prev = n;
/* 注意一下!
* 有人是这么说的:其中,h、n分别为两个队列的指针,即头节点指针,该操作将n队列链接在h队列之后。
* 也有人表示完全看不懂~~
* 我其实也是看不懂的,画了好久,同学们可以自己画一下,我感觉n是一个单独的节点,
* 然后把h远远连在前面(中间隔n个节点),把q连在后面(中间不隔节点),然后变成了一个环(大家画的时候要注意每个链表都是双向环形的)。
* 好吧,不好意思我也没弄对,应该是这样:
* 大家动手画一画吧,这个因为源码也没注释~~
*/

#define ngx_queue_add(h, n)                                                   \
(h)->prev->next = (n)->next;                                              \
(n)->next->prev = (h)->prev;                                              \
(h)->prev = (n)->prev;                                                    \
(h)->prev->next = h;
/* h、n分别为两个队列的指针,即头节点指针,该操作将n队列链接在h队列之后。*/

#define ngx_queue_data(q, type, link)                                         \
(type *) ((u_char *) q - offsetof(type, link))
/* 依靠内存位置的off_t来得到相应节点元素数据的内存地址,q为当前地址,type为相应结构体的类型,link为queue */


另外两个非宏的函数:

/*
* find the middle queue element if the queue has odd number of elements
* or the first element of the queue’s second part otherwise
*/
ngx_queue_t *
ngx_queue_middle(ngx_queue_t *queue)
{
ngx_queue_t  *middle, *next;
middle = ngx_queue_head(queue);
if (middle == ngx_queue_last(queue)) {
return middle;
}
next = ngx_queue_head(queue);
for ( ;; ) {
middle = ngx_queue_next(middle);
next = ngx_queue_next(next);
// 偶数个节点,在此返回后半个队列的第一个节点
if (next == ngx_queue_last(queue)) {
return middle;
}
//奇数个节点,在此返回中间节点
next = ngx_queue_next(next);
if (next == ngx_queue_last(queue)) {
return middle;
}
}
}
/* 这个函数是用来获取列表最中间那个值的,使用了快慢指针的方法,mid移动1位,next就移动2位,返回mid */

void
ngx_queue_sort(ngx_queue_t *queue,
ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *));
/* 使用了简单的插入排序算法,是一个稳定的排序 */
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: