菜鳥nginx源碼剖析數據結構篇(二) 雙向鏈表ngx_queue_t[轉]

nginx源碼剖析數據結構篇(二) 雙向鏈表ngx_queue_tlinux

 

  • Author:Echo Chen(陳斌)ios

  • Email:chenb19870707@gmail.comnginx

  • Blog:Blog.csdn.net/chen19870707面試

  • Date:October 20h, 2014算法

     

    1.ngx_queue優點和特色

     

    ngx_queue做爲順序容器鏈表,它優點在於其能夠高效地執行插入、刪除、合併操做,在插入刪除的過程當中,只須要修改指針指向,而不須要拷貝數據,所以,對於頻繁修改的容器很適合。此外,相對於STL list,它還具備如下特色:數據結構

    • 自身實現了排序功能
    • 輕量級,不負責內存的分配
    • 自身支持兩個鏈表的合併

    2.源代碼位置

     

    頭文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_queue.happ

    源文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_queue.c函數

     

    3.數據結構定義

     

       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

     

    4.初始化ngx_queue_init

     

       1: //q 爲鏈表容器結構體ngx_queue_t的指針,將頭尾指針指向本身
       2: #define ngx_queue_init(q)                                                     \
       3:     (q)->prev = q;                                                            \
       4:     (q)->next = q

     

    初始狀態的鏈表如圖所示:.net

    image

     

    5.判斷鏈表容器是否爲空ngx_queue_empty

     

    判斷方法很是簡單,即判斷鏈表的prev指針是否指向本身,如上圖所示

       1: #define ngx_queue_empty(h)                                                    \
       2:     (h == (h)->prev)

     

     

    6.頭部插入ngx_queue_insert_head

     

       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

     

    標準的雙鏈表插入四步操做,如圖所示:

    image

     

    7.尾部插入ngx_queue_insert_tail

     

    與頭部插入相似,只是第一步給的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

     

    8.鏈表刪除ngx_queue_remove

     

    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:  
     
       

     

    9.鏈表拆分ngx_queue_split

     

       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爲首元素,操做也很簡單,如圖所示:

    048644D883FB5C91A33920AE9345A329

     

    10.鏈表合併ngx_queue_add

     

       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的尾部,如圖所示:

    8DBB8AC04328FBB9A11F0F4A856E65EA

     

    11. 鏈表中心元素ngx_queue_middle

     

       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),這是一道經典的面試題,今天在這裏看到了源碼,似成相識啊,果真經典面試題目都不是憑空而來。

     

     

    12.鏈表排序ngx_queue_sort

     

    能夠看到,這裏採用的是插入排序算法,時間複雜度爲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: }

     

    13.根據ngx_queue_t 找到鏈表元素

     

       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中的偏移。

     

     

    14.其它方法

     

       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

     

    15.實戰

     

       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: }

     

    輸出結果:

    image

     

    16.總結

         

           ngx_queue設計很是精巧,基本涵蓋了雙鏈表的全部操做,建議須要面試的童鞋看一看,不少鏈表的題目都迎刃而解。此外,ngx_queue與其它nginx 代碼耦合度低,有須要這種雙向鏈表的實現時不妨直接拿過來使用。

     

    -

相關文章
相關標籤/搜索