菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t

  • Author:Echo Chen(陈斌)
  • Email:[email protected]
  • Blog:Blog.csdn.net/chen19870707

    Date:October 23h, 2014

    1.ngx_list优势和特点

    ngx_list _t是一个顺序容器,它实际上是动态数组和单向链表的结合体,扩容起来比动态数组简单的多,可以一次扩容一个数组,所以说它结合了 链表插入删除不需要移动的 和 数组下标快速索引 的优势,设计非常让人叫绝,此外它还有以下特点:

    • 链表中存储的元素是灵活的,可以是任何一种数据结构。
    • 链表元素需要占用的内存由ngx_list_t管理,它已经通过数组分配好了。
    • 小块的内存使用链表访问O(n)效率是低下的,可以使用数组通过直接通过偏移量来直接访问O(1)。

    2.源代码位置

    头文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_list.h

    源文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_list.c

    3.数据结构定义

    前面说到ngx_list_t是一个数组链表,链表中的每个结点都是一个数组,ngx_list_part_t 描述的是链表中的一个结点,这个结点又是一个数组,elts为数组首地址,nelts为该数组已经使用的个数,*next为下一个链表结点的指针,定义如下:

       1: typedef struct ngx_list_part_s  ngx_list_part_t;
       2:  
       3: //描述链表中的一个结点,这个结点又是一个数组
       4: struct ngx_list_part_s {
       5:     void             *elts;     //首地址
       6:     ngx_uint_t        nelts;    //已经使用的个数
       7:     ngx_list_part_t  *next;     //下一个链表节点的指针
       8: };

    ngx_list_t是描述整个链表,其中last 为链表中最后一个数组,part为链表中首个数组,size为数组每个元素占用空间小,nalloc为每个数组可以容纳的元素个数,pool为链表内存池对象,定义如下:

       1: typedef struct {
       2:     ngx_list_part_t  *last;        //链表中最后一个数组元素
       3:     ngx_list_part_t   part;        //链表中的首个数组元素
       4:     size_t            size;        //每个数组元素占用的空间大小
       5:     ngx_uint_t        nalloc;      //每个数组结点的容量,即每个数组最多可以存放多少个元素
       6:     ngx_pool_t       *pool;        //链表中的内存池对象指针
       7: } ngx_list_t;

    其结构如下图所示,最下面一行为内存映像,可以看到整个内存中仅仅多了 一个ngx_list_t 和 几个ngx_list_part_s结果,内存并没有太多的浪费,nginx在内存方面的苛刻确实值得我们学习

    4.链表创建ngx_list_create和初始化ngx_list_init

       1: //链表创建,pool为内存池对象,size为每个数组元素的大小,n为每个数组可以容纳的元素个数
       2: ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
       3: {
       4:     ngx_list_t  *list;
       5:     
       6:     //分配ngx_list_t结构
       7:     list = ngx_palloc(pool, sizeof(ngx_list_t));
       8:     if (list == NULL) {
       9:         return NULL;
      10:     }
      11:  
      12:     //初始化
      13:     if (ngx_list_init(list, pool, n, size) != NGX_OK) {
      14:         return NULL;
      15:     }
      16:  
      17:     return list;
      18: }
      19:  
      20: //链表初始化,list为链表结构(在create中创建),pool为内存池,size为每个数组元素的大小,n为每个数组可以容纳的元素个数
      21: static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
      22: {
      23:     //分配一个数组,并用链表首结点指向它
      24:     list->part.elts = ngx_palloc(pool, n * size);
      25:     if (list->part.elts == NULL) {
      26:         return NGX_ERROR;
      27:     }
      28:  
      29:     //已使用个数为0
      30:     list->part.nelts = 0;
      31:     //没有下一个结点,next指为NULL
      32:     list->part.next = NULL;
      33:     //最后一个结点也指向刚刚分配的数组
      34:     list->last = &list->part;
      35:     //数组每个元素大小为size
      36:     list->size = size;
      37:     //数组能容纳元素个数为n
      38:     list->nalloc = n;
      39:     //内存池为pool
      40:     list->pool = pool;
      41:  
      42:     return NGX_OK;
      43: }

    可以看到,ngx_list_init调用成功后,会创建一个数组结点。

    5.链表添加元素操作ngx_list_push

       1: //链表添加元素,l为链表结构
       2: void *ngx_list_push(ngx_list_t *l)
       3: {
       4:     void             *elt;
       5:     ngx_list_part_t  *last;
       6:  
       7:     //last指向链表最后一个结点
       8:     last = l->last;
       9:  
      10:     //如果最后一个结点的数组已经满了
      11:     if (last->nelts == l->nalloc) {
      12:  
      13:         /* the last part is full, allocate a new list part */
      14:         
      15:         //从内存池中分配一个ngx_list_part_t数组对象
      16:         last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
      17:         if (last == NULL) {
      18:             return NULL;
      19:         }
      20:         
      21:         //从内存池中给数组对象元素分配空间,每个元素大小为size,元素数目为nalloc
      22:         last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
      23:         if (last->elts == NULL) {
      24:             return NULL;
      25:         }
      26:  
      27:         //新分配的数组已使用结点个数为0,下一个链表结点为NULL
      28:         last->nelts = 0;
      29:         last->next = NULL;
      30:  
      31:         //将新分配的数组链到链表,并把链表尾指针指向新分配数组
      32:         l->last->next = last;
      33:         l->last = last;
      34:     }
      35:     
      36:     //elts为数组首地址,elts + size * nelts 即位为分配数组元素的位置
      37:     elt = (char *) last->elts + l->size * last->nelts;
      38:     
      39:     //分配个数+1
      40:     last->nelts++;
      41:  
      42:     return elt;
      43: }

    可以看到,push的思想很简单,每次都往链表最后一个数组添加,数组满后就再增加一个数组结点,这里push的时间复杂度为O(1),没有任何遍历操作,充分利用了
    “链表尾插”
    “数组下标索引”的优势。

    6.疑问

    源代码中只给了链表的插入操作,没有删除操作,看起来不完整,这里猜测这个链表结构应该不会直接使用,而是被再次封装。

    7.实战

    看完源代码,这个list设计还是很巧妙的,结合了数组和链表各自的优势,确实不得不让人拍手叫绝。下面尝试给出ngx_list_t的使用例子:

       1: int main()
       2: {
       3:     ngx_list_t *pList = ngx_list_create( r->pool,4,sizeof( ngx_str_t));
       4:     if( NULL == pList)
       5:     {
       6:         return -1;
       7:     } 
       8:     
       9:     ngx_str_t *str = ngx_list_push(pList);
      10:     if( NULL == str)
      11:     {
      12:         return -1;
      13:     }
      14:  
      15:     str->len = sizeof("This is a test for ngx_list_t");
      16:     str->value = "This is a test for ngx_list_t";
      17:  
      18:     //遍历
      19:      ngx_list_part_t *part = &pList->part;
      20:     ngx_str_t *str = part->elts;
      21:     
      22:     for(int i = 0;;i++)
      23:     {
      24:         if( i >= part->nelts)
      25:         {   
      26:             //已经遍历完最后一个数组,没有下一个结点了
      27:             if( NULL == part->next)
      28:             {
      29:                 break;
      30:             }
      31:             
      32:             //还有下一个数组,part指到下一个数组,并把str指到新数组的地址
      33:             part = part->next;
      34:             str = part->elts;
      35:            
      36:             //重置计数器
      37:             i = 0;
      38:          }
      39:  
      40:         //输出结点
      41:         cout << "list elemts :" << str[i] << endl;
      42:         
      43:     }

    -

    Echo Chen:Blog.csdn.net/chen19870707

    -

  • 时间: 2024-11-06 09:36:35

    菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t的相关文章

    菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t[转]

    菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 23h, 2014 1.ngx_list优势和特点 ngx_list _t是一个顺序容器,它实际上是动态数组和单向链表的结合体,扩容起来比动态数组简单的多,可以一次扩容一个数组,所以说它结合了 链表插入删除不需要移动的 和 数组下标快速索引 的优势

    菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)

    Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 31h, 2014 1.哈希表ngx_hash_t的优势和特点 哈希表是一种典型的以空间换取时间的数据结构,在没有冲突的情况下,对任意元素的插入.索引.删除的时间复杂度都是O(1).这样优秀的时间复杂度是通过将元素的key值以hash方法f映射到哈希表中的某一个位置来访问记录来实现的,即键值为key的元素必定存储在哈希

    菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t[转]

    菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 11th, 2014 今天是一年一度的光棍节,还没有女朋友的程序猿童鞋不妨new一个出来,内存管理一直是C/C++中最棘手的部分,远不止new/delete.malloc/free这么简单.随着代码量的递增,程序结构复杂度的提高.今天我们就一起研究一下以精巧著

    菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t[转]

    菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 14th, 2014 1.共享内存 共享内存是Linux下提供的最基本的进程通信方法,它通过mmap或者shmget系统调用在内存中创建了一块连续的线性地址空间,而通过munmap或者shmdt系统调用释放这块内存,使用共享内存的好处是多个进程使用同一块内存

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表ngx_chain_t[转]

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 6th, 2014 1.缓冲区链表结构ngx_chain_t和ngx_buf_t nginx的缓冲区链表如下图所示,ngx_chain_t为链表,ngx_buf_t为缓冲区结点: 2.源代码位置 头文件:http://trac.nginx.org/ng

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表ngx_chain_t

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 6th, 2014 1.缓冲区链表结构ngx_chain_t和ngx_buf_t nginx的缓冲区链表如下图所示,ngx_chain_t为链表,ngx_buf_t为缓冲区结点: 2.源代码位置 头文件:http://trac.nginx.org/ng

    菜鸟nginx源码剖析数据结构篇(一) 动态数组ngx_array_t

    菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 20h, 2014 1.ngx_array优势和特点 ngx_array _t是一个顺序容器,支持达到数组容量上限时动态改变数组的大小,类似于STL中vector,具有以下特性: 下标直接索引,访问速度快 动态增长 由slab内存池统一管理分配出的内

    菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t[转]

    菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 27h, 2014 1.ngx_rbtree优势和特点 ngx_rbtree是一种使用红黑树实现的关联容器,关于红黑树的特性,在<手把手实现红黑树>已经详细介绍,这里就只探讨ngx_rbtree与众不同的地方:ngx_rbtree红黑树容器中的元素

    菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t[转]

    菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 28h, 2014 1.什么是基数树 基数树(radix tree)是一种不怎么常见的数据结构,这里简单的做一下介绍:在计算机科学中,基数树,是一种基于trie(字典树)的特殊的数据结构,可以快速定位叶子结点.radix tree是一种多

    菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t[转]

    菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 20h, 2014 1.ngx_array优势和特点 ngx_array _t是一个顺序容器,支持达到数组容量上限时动态改变数组的大小,类似于STL中vector,具有以下特性: 下标直接索引,访问速度快 动态增长 由slab内存池统一管理分配出的内