nginx源碼剖析數據結構篇(二) 雙向鏈表ngx_queue_tlinux
Author:Echo Chen(陳斌)ios
Email:chenb19870707@gmail.comnginx
Blog:Blog.csdn.net/chen19870707面試
Date:October 20h, 2014算法
ngx_queue做爲順序容器鏈表,它優點在於其能夠高效地執行插入、刪除、合併操做,在插入刪除的過程當中,只須要修改指針指向,而不須要拷貝數據,所以,對於頻繁修改的容器很適合。此外,相對於STL list,它還具備如下特色:數據結構
頭文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_queue.happ
源文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_queue.c函數
1: typedef struct ngx_queue_s ngx_queue_t;
2:
3: struct ngx_queue_s {
4: ngx_queue_t *prev;
5: ngx_queue_t *next;
6: };
能夠看到,它的結構很是簡單,僅有兩個成員:prev、next,這樣對於鏈表中元素來講,空間上只增長了兩個指針的消耗。spa
1: //q 爲鏈表容器結構體ngx_queue_t的指針,將頭尾指針指向本身
2: #define ngx_queue_init(q) \
3: (q)->prev = q; \
4: (q)->next = q
初始狀態的鏈表如圖所示:.net
判斷方法很是簡單,即判斷鏈表的prev指針是否指向本身,如上圖所示
1: #define ngx_queue_empty(h) \
2: (h == (h)->prev)
1: //h爲鏈表指針,x爲要插入的元素
2: #define ngx_queue_insert_head(h, x) \
3: (x)->next = (h)->next; \
4: (x)->next->prev = x; \
5: (x)->prev = h; \
6: (h)->next = x
標準的雙鏈表插入四步操做,如圖所示:
與頭部插入相似,只是第一步給的h->prev ,即爲最後一個結點:
1: #define ngx_queue_insert_tail(h, x) \
2: (x)->prev = (h)->prev; \
3: (x)->prev->next = x; \
4: (x)->next = h; \
5: (h)->prev = x
x爲要刪除的結點,將x的下一個的結點的prev指針指向x的上一個結點,再將x的前一個結點的next指針指向x的下一個結點,常規鏈表雙鏈表結點刪除操做,不處理內存釋放
1: #define ngx_queue_remove(x) \
2: (x)->next->prev = (x)->prev; \
3: (x)->prev->next = (x)->next
4:
1: #define ngx_queue_split(h, q, n) \
2: (n)->prev = (h)->prev; \
3: (n)->prev->next = n; \
4: (n)->next = q; \
5: (h)->prev = (q)->prev; \
6: (h)->prev->next = h; \
7: (q)->prev = n;
h爲鏈表容器,q爲鏈表h中的一個元素,這個方法能夠將鏈表h以元素q爲界拆分爲兩個鏈表h和n,其中h由原鏈表的前半部分組成(不包含q),而n由後半部分組成,q爲首元素,操做也很簡單,如圖所示:
1: #define ngx_queue_add(h, n) \
2: (h)->prev->next = (n)->next; \
3: (n)->next->prev = (h)->prev; \
4: (h)->prev = (n)->prev; \
5: (h)->prev->next = h;
將鏈表n 合併到鏈表h的尾部,如圖所示:
1: ngx_queue_t *ngx_queue_middle(ngx_queue_t *queue)
2: {
3: ngx_queue_t *middle, *next;
4:
5: middle = ngx_queue_head(queue);
6:
7: //頭尾相等,空鏈表,返回頭便可
8: if (middle == ngx_queue_last(queue)) {
9: return middle;
10: }
11:
12: next = ngx_queue_head(queue);
13:
14: for ( ;; ) {
15: middle = ngx_queue_next(middle);
16: next = ngx_queue_next(next);
17:
18: if (next == ngx_queue_last(queue)) {
19: return middle;
20: }
21:
22: next = ngx_queue_next(next);
23:
24: if (next == ngx_queue_last(queue)) {
25: return middle;
26: }
27: }
28: }
這裏用到的技巧是每次middle向後移動一步,next向後移動兩步,這樣next指到隊尾的時候,middle就指到了中間,時間複雜度就是O(N),這是一道經典的面試題,今天在這裏看到了源碼,似成相識啊,果真經典面試題目都不是憑空而來。
能夠看到,這裏採用的是插入排序算法,時間複雜度爲O(n),整個代碼很是簡潔。
1: void ngx_queue_sort(ngx_queue_t *queue, ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *))
2: {
3: ngx_queue_t *q, *prev, *next;
4:
5: q = ngx_queue_head(queue);
6:
7: //若是是空鏈表,直接返回
8: if (q == ngx_queue_last(queue)) {
9: return;
10: }
11:
12: for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) {
13:
14: prev = ngx_queue_prev(q);
15: next = ngx_queue_next(q);
16:
17: ngx_queue_remove(q);
18:
19: //找到插入位置
20: do {
21: if (cmp(prev, q) <= 0) {
22: break;
23: }
24:
25: prev = ngx_queue_prev(prev);
26:
27: } while (prev != ngx_queue_sentinel(queue));
28:
29: //插入
30: ngx_queue_insert_after(prev, q);
31: }
32: }
1: #define ngx_queue_data(q, type, link) \
2: (type *) ((u_char *) q - offsetof(type, link))
其中q爲ngx_queue_t* 類型,函數做用爲根據q算出,算出鏈表元素的地址,其中linux接口offsetof是算出link在type中的偏移。
1: #define ngx_queue_head(h) \
2: (h)->next
3:
4:
5: #define ngx_queue_last(h) \
6: (h)->prev
7:
8:
9: #define ngx_queue_sentinel(h) \
10: (h)
11:
12:
13: #define ngx_queue_next(q) \
14: (q)->next
15:
16:
17: #define ngx_queue_prev(q) \
18: (q)->prev
1: #include <iostream>
2: #include <algorithm>
3: #include <pthread.h>
4: #include <time.h>
5: #include <stdio.h>
6: #include <errno.h>
7: #include <string.h>
8: #include "ngx_queue.h"
9:
10: struct student_info
11: {
12: long stu_id;
13: unsigned int age;
14: unsigned int score;
15: ngx_queue_t qEle;
16: };
17:
18: ngx_int_t compareStudent(const ngx_queue_t *a, const ngx_queue_t *b)
19: {
20: //分別取得a b 對象指針
21: student_info *ainfo = ngx_queue_data(a,student_info,qEle);
22: student_info *binfo = ngx_queue_data(b,student_info,qEle);
23:
24: return ainfo->score >binfo->score;
25: }
26:
27: void print_ngx_queue(ngx_queue_t *queue)
28: {
29: //遍歷輸出
30: for(ngx_queue_t *q = ngx_queue_head(queue);q != ngx_queue_sentinel(queue);q = ngx_queue_next(q))
31: {
32: student_info *info = ngx_queue_data(q,student_info,qEle);
33: if(info != NULL)
34: {
35: std::cout <<info->score << " ";
36: }
37: }
38:
39: std::cout << std::endl;
40: }
41:
42: int main()
43: {
44:
45: ngx_queue_t queue;
46: ngx_queue_init(&queue);
47:
48: student_info info[5];
49: for(int i = 0;i < 5;i++)
50: {
51: info[i].stu_id = i;
52: info[i].age = i;
53: info[i].score = i;
54:
55: if(i%2)
56: {
57: ngx_queue_insert_tail(&queue,&info[i].qEle);
58: }
59: else
60: {
61: ngx_queue_insert_head(&queue,&info[i].qEle);
62: }
63: }
64:
65: print_ngx_queue(&queue);
66:
67: ngx_queue_sort(&queue,compareStudent);
68:
69: print_ngx_queue(&queue);
70:
71: return 0;
72: }
輸出結果:
ngx_queue設計很是精巧,基本涵蓋了雙鏈表的全部操做,建議須要面試的童鞋看一看,不少鏈表的題目都迎刃而解。此外,ngx_queue與其它nginx 代碼耦合度低,有須要這種雙向鏈表的實現時不妨直接拿過來使用。
-