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

  • Author:Echo Chen(陈斌)

  • Email:chenb19870707@gmail.com

  • Blog:Blog.csdn.net/chen19870707

  • Date:Nov 3rd, 2014

    在前面一篇文章《菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)》 里介绍了 普通哈希表 和 带有通配符的哈希表 的基本结构和初始化方法,由于篇幅的原因未能解析完结,这篇继续解源码中剩余的部分。

  • 1.普通哈希表ngx_hash_t查找 ngx_hash_find

    普通哈希表的查找比较简单,思想就是先根据hash值找到对应桶,然后遍历这个桶的每一个元素,逐字匹配是否关键字完全相同,完全相同则找到,否则继续,直至找到这个桶的结尾(value = NULL)。

       1: /* @hash 表示哈希表的结构体
       2:  * @key  表示根据哈希方法计算出来的hash值
       3:  * @name 表示实际关键字地址
       4:  * @len  表示实际关键字长度
       5:  */
       6: void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
       7: {
       8:     ngx_uint_t       i;
       9:     ngx_hash_elt_t  *elt;
      10:  
      11:     //根据hash值找到桶索引
      12:     elt = hash->buckets[key % hash->size];
      13:  
      14:     if (elt == NULL) {
      15:         return NULL;
      16:     }
      17:  
      18:     //桶结束的标志为 value 为 NULL
      19:     while (elt->value) {
      20:         //关键字长度不匹配,下一个
      21:         if (len != (size_t) elt->len) {
      22:             goto next;
      23:         }
      24:         
      25:         //遍历关键字每一个字符,若全部对得上,则找到,否则有一个不同下一个
      26:         for (i = 0; i < len; i++) {
      27:             if (name[i] != elt->name[i]) {
      28:                 goto next;
      29:             }
      30:         }
      31:  
      32:         return elt->value;
      33:  
      34:     next:
      35:         //从elt关键字开始向后移动关键字长度个,并行对齐,即为下一个ngx_hash_elt_t
      36:         elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
      37:                                                sizeof(void *));
      38:         continue;
      39:     }
      40:  
      41:     return NULL;
      42: }

2.支持通配符哈希表的前置通配符查找 ngx_hash_find_wc_head

还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下图所示

  • 00 - value 是 "example.com" 和 "*.example.com"的数据指针
  • 01 - value 仅仅是 "*.example.com"的数据指针
  • 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指针
  • 11 - value 仅仅是 "*.example.com"的指针 

查找的思路就是根据value的指针信息来搜索,源代码如下:

   1: /* @hwc  表示支持通配符的哈希表的结构体
   2:  * @name 表示实际关键字地址
   3:  * @len  表示实际关键字长度
   4:  */
   5: void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
   6: {
   7:     void        *value;
   8:     ngx_uint_t   i, n, key;
   9:  
  10:     n = len;
  11:     
  12:     //从后往前搜索第一个dot,则n 到 len-1 即为关键字中最后一个 子关键字
  13:     while (n) {
  14:         if (name[n - 1] == '.') {
  15:             break;
  16:         }
  17:  
  18:         n--;
  19:     }
  20:  
  21:     key = 0;
  22:     
  23:     //n 到 len-1 即为关键字中最后一个 子关键字,计算其hash值
  24:     for (i = n; i < len; i++) {
  25:         key = ngx_hash(key, name[i]);
  26:     }
  27:     
  28:     //调用普通查找找到关键字的value(用户自定义数据指针)
  29:     value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);
  30:  
  31:     /**还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下:
  32:      * 00 - value 是 "example.com" 和 "*.example.com"的数据指针
  33:      * 01 - value 仅仅是 "*.example.com"的数据指针
  34:      * 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指针
  35:      * 11 - value 仅仅是 "*.example.com"的指针
  36:      */
  37:     if (value)
  38:     {
  39:  
  40:         if ((uintptr_t) value & 2) {
  41:             
  42:             //搜索到了最后一个子关键字且没有通配符,如"example.com"的example
  43:             if (n == 0) {
  44:                 //value低两位为11,仅为"*.example.com"的指针,这里没有通配符,没招到,返回NULL
  45:                 if ((uintptr_t) value & 1) {
  46:                     return NULL;
  47:                 }
  48:                 
  49:                 //value低两位为10,为"example.com"的指针,value就在下一级的ngx_hash_wildcard_t 的value中,去掉携带的低2位11
  50:                 hwc = (ngx_hash_wildcard_t *)
  51:                                           ((uintptr_t) value & (uintptr_t) ~3);
  52:                 return hwc->value;
  53:             }
  54:             
  55:             //还未搜索完,低两位为11或10,继续去下级ngx_hash_wildcard_t中搜索
  56:             hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
  57:             
  58:             //继续搜索 关键字中剩余部分,如"example.com",搜索 0 到 n -1 即为 example
  59:             value = ngx_hash_find_wc_head(hwc, name, n - 1);
  60:  
  61:             //若找到,则返回
  62:             if (value) 
  63:             {
  64:                 return value;
  65:             }
  66:             
  67:             //低两位为00 找到,即为wc->value
  68:             return hwc->value;
  69:         }
  70:  
  71:         //低两位为01
  72:         if ((uintptr_t) value & 1) 
  73:         {
  74:             //关键字没有通配符,错误返回空
  75:             if (n == 0) 
  76:             {
  77:                 return NULL;
  78:             }
  79:             
  80:             //有通配符,直接返回
  81:             return (void *) ((uintptr_t) value & (uintptr_t) ~3);
  82:         }
  83:  
  84:         //低两位为00,直接返回
  85:         return value;
  86:     }
  87:  
  88:     return hwc->value;
  89: }

3.支持通配符哈希表的后置通配符查找 ngx_hash_find_wc_tail

ngx_hash_find_wc_tail与前置通配符查找差不多,这里value低两位仅有两种标志,更加简单:

  • 00 - value 是指向 用户自定义数据
  • 11 - value的指向下一个哈希表 
    源代码如下:
   1: void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
   2: {
   3:     void        *value;
   4:     ngx_uint_t   i, key;
   5:  
   6:  
   7:     key = 0;
   8:  
   9:     //从前往前搜索第一个dot,则0 到 i 即为关键字中第一个 子关键字
  10:     for (i = 0; i < len; i++)
  11:     {
  12:         if (name[i] == '.')
  13:         {
  14:             break;
  15:         }
  16:         //计算哈希值
  17:         key = ngx_hash(key, name[i]);
  18:     }
  19:  
  20:     //没有通配符,返回NULL
  21:     if (i == len)
  22:     {
  23:         return NULL;
  24:     }
  25:  
  26:     //调用普通查找找到关键字的value(用户自定义数据指针)
  27:     value = ngx_hash_find(&hwc->hash, key, name, i);
  28:  
  29:  
  30:     /**还记得上节在ngx_hash_wildcard_init中,用value指针低2位来携带信息吗?其是有特殊意义的,如下:
  31:      * 00 - value 是数据指针
  32:      * 11 - value的指向下一个哈希表
  33:      */
  34:     if (value)
  35:     {
  36:         //低2位为11,value的指向下一个哈希表,递归搜索
  37:         if ((uintptr_t) value & 2)
  38:         {
  39:  
  40:             i++;
  41:  
  42:             hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
  43:  
  44:             value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
  45:  
  46:             //找到低两位00,返回
  47:             if (value) 
  48:             {
  49:                 return value;
  50:             }
  51:             
  52:             //找打低两位11,返回hwc->value
  53:             return hwc->value;
  54:         }
  55:  
  56:         return value;
  57:     }
  58:  
  59:     //低2位为00,直接返回数据
  60:     return hwc->value;
  61: }

4.组合哈希表查找 ngx_hash_find_combined

组合哈希表的查找思路非常简单,先在普通哈希表中查找,没找到再去前置通配符哈希表中查找,最后去后置通配符哈希表中查找,源代码如下:

   1: void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name,size_t len)
   2: {
   3:     void  *value;
   4:     
   5:     //在普通hash表中查找
   6:     if (hash->hash.buckets) {
   7:         value = ngx_hash_find(&hash->hash, key, name, len);
   8:  
   9:         if (value) {
  10:             return value;
  11:         }
  12:     }
  13:  
  14:     if (len == 0) {
  15:         return NULL;
  16:     }
  17:     
  18:     //在前置通配符哈希表中查找
  19:     if (hash->wc_head && hash->wc_head->hash.buckets) {
  20:         value = ngx_hash_find_wc_head(hash->wc_head, name, len);
  21:  
  22:         if (value) {
  23:             return value;
  24:         }
  25:     }
  26:     
  27:     //在后置通配符哈希表中查找
  28:     if (hash->wc_tail && hash->wc_tail->hash.buckets) {
  29:         value = ngx_hash_find_wc_tail(hash->wc_tail, name, len);
  30:  
  31:         if (value) {
  32:             return value;
  33:         }
  34:     }
  35:  
  36:     return NULL;
  37: }

5.支持通配符哈希表初始化 ngx_hash_wildcard_init

首先看一下ngx_hash_wildcard_init 的内存结构,当构造此类型的hash表的时候,实际上是构造了一个hash表的一个“链表”,是通过hash表中的key“链接”起来的。比如:对于“*.abc.com”将会构造出2个hash表,第一个hash表中有一个key为com的表项,该表项的value包含有指向第二个hash表的指针,而第二个hash表中有一个表项abc,该表项的value包含有指向*.abc.com对应的value的指针。那么查询的时候,比如查询www.abc.com的时候,先查com,通过查com可以找到第二级的hash表,在第二级hash表中,再查找abc,依次类推,直到在某一级的hash表中查到的表项对应的value对应一个真正的值而非一个指向下一级hash表的指针的时候,查询过程结束。

理解了这个,我们就可以看源代码了,ngx_hash_wildcard是一个递归函数,递归创建如上图的hash链表,如下为注释版源代码。

精彩的读点有:

  • 由于指针都字节对齐了,低4位肯定为0,这种操作(name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2)) ) 巧妙的使用了指针的低位携带额外信息,节省了内存,让人不得不佩服ngx设计者的想象力。
   1: /*hinit为初始化结构体指针,names为预加入哈希表数组,elts为预加入数组大小
   2: 特别要注意的是这里的key已经都是被预处理过的。例如:“*.abc.com”或者“.abc.com”被预处理完成以后,
   3: 变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”*/
   4: ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,ngx_uint_t nelts)
   5: {
   6:     size_t                len, dot_len;
   7:     ngx_uint_t            i, n, dot;
   8:     ngx_array_t           curr_names, next_names;
   9:     ngx_hash_key_t       *name, *next_name;
  10:     ngx_hash_init_t       h;
  11:     ngx_hash_wildcard_t  *wdc;
  12:  
  13:     //初始化临时动态数组curr_names,curr_names是存放当前关键字的数组
  14:     if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
  15:                        sizeof(ngx_hash_key_t))
  16:         != NGX_OK)
  17:     {
  18:         return NGX_ERROR;
  19:     }
  20:  
  21:     //初始化临时动态数组next_names,next_names是存放关键字去掉后剩余关键字
  22:     if (ngx_array_init(&next_names, hinit->temp_pool, nelts, sizeof(ngx_hash_key_t)) != NGX_OK)
  23:     {
  24:         return NGX_ERROR;
  25:     }
  26:  
  27:     //遍历 names 数组
  28:     for (n = 0; n < nelts; n = i)
  29:     {
  30:         dot = 0;
  31:  
  32:         //查找 dot
  33:         for (len = 0; len < names[n].key.len; len++)
  34:         {
  35:             if (names[n].key.data[len] == '.')
  36:             {
  37:                 dot = 1;
  38:                 break;
  39:             }
  40:         }
  41:  
  42:         //将关键字dot以前的关键字放入curr_names
  43:         name = ngx_array_push(&curr_names);
  44:         if (name == NULL) {
  45:             return NGX_ERROR;
  46:         }
  47:  
  48:         name->key.len = len;
  49:         name->key.data = names[n].key.data;
  50:         name->key_hash = hinit->key(name->key.data, name->key.len);
  51:         name->value = names[n].value;
  52:  
  53:         dot_len = len + 1;
  54:  
  55:         //len指向dot后剩余关键字
  56:         if (dot)
  57:         {
  58:             len++;
  59:         }
  60:  
  61:         next_names.nelts = 0;
  62:  
  63:         //如果names[n] dot后还有剩余关键字,将剩余关键字放入next_names中
  64:         if (names[n].key.len != len)
  65:         {
  66:             next_name = ngx_array_push(&next_names);
  67:             if (next_name == NULL) {
  68:                 return NGX_ERROR;
  69:             }
  70:  
  71:             next_name->key.len = names[n].key.len - len;
  72:             next_name->key.data = names[n].key.data + len;
  73:             next_name->key_hash = 0;
  74:             next_name->value = names[n].value;
  75:  
  76:         }
  77:  
  78:         //如果上面搜索到的关键字没有dot,从n+1遍历names,将关键字比它长的全部放入next_name
  79:         for (i = n + 1; i < nelts; i++)
  80:         {
  81:             //前len个关键字相同
  82:             if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0) {
  83:                 break;
  84:             }
  85:  
  86:  
  87:             if (!dot
  88:                 && names[i].key.len > len
  89:                 && names[i].key.data[len] != '.')
  90:             {
  91:                 break;
  92:             }
  93:  
  94:             next_name = ngx_array_push(&next_names);
  95:             if (next_name == NULL) {
  96:                 return NGX_ERROR;
  97:             }
  98:  
  99:             next_name->key.len = names[i].key.len - dot_len;
 100:             next_name->key.data = names[i].key.data + dot_len;
 101:             next_name->key_hash = 0;
 102:             next_name->value = names[i].value;
 103:  
 104:         }
 105:  
 106:         //如果next_name非空
 107:         if (next_names.nelts)
 108:         {
 109:             h = *hinit;
 110:             h.hash = NULL;
 111:  
 112:             //递归,创建一个新的哈西表
 113:             if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,next_names.nelts) != NGX_OK)
 114:             {
 115:                 return NGX_ERROR;
 116:             }
 117:  
 118:             wdc = (ngx_hash_wildcard_t *) h.hash;
 119:             
 120:             //如上图,将用户value值放入新的hash表
 121:             if (names[n].key.len == len)
 122:             {
 123:                 wdc->value = names[n].value;
 124:             }
 125:             
 126:             //并将当前value值指向新的hash表
 127:             name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
 128:  
 129:         } else if (dot)
 130:         {
 131:             name->value = (void *) ((uintptr_t) name->value | 1);
 132:         }
 133:     }
 134:  
 135:     //将最外层hash初始化
 136:     if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts,curr_names.nelts) != NGX_OK)
 137:     {
 138:         return NGX_ERROR;
 139:     }
 140:  
 141:     return NGX_OK;
 142: }

6. 哈希键数组初始化 ngx_hash_keys_array_init

初始化ngx_hash_keys_arrays_t 结构体,type的取值范围只有两个,NGX_HASH_SMALL表示初始化元素较少,NGX_HASH_LARGE表示初始化元素较多,在向ha中加入时必须调用此方法

   1: ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
   2: {
   3:     ngx_uint_t  asize;
   4:  
   5:     if (type == NGX_HASH_SMALL)
   6:     {
   7:         asize = 4;
   8:         ha->hsize = 107;
   9:     }
  10:     else
  11:     {
  12:         asize = NGX_HASH_LARGE_ASIZE;
  13:         ha->hsize = NGX_HASH_LARGE_HSIZE;
  14:     }
  15:  
  16:     //初始化 存放非通配符关键字的数组
  17:     if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
  18:     {
  19:         return NGX_ERROR;
  20:     }
  21:  
  22:     //初始化 存放前置通配符处理好的关键字 数组
  23:     if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
  24:     {
  25:         return NGX_ERROR;
  26:     }
  27:  
  28:     //初始化 存放后置通配符处理好的关键字 数组
  29:     if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))!= NGX_OK)
  30:     {
  31:         return NGX_ERROR;
  32:     }
  33:  
  34:     /*初始化 二位数组 ,这个数组存放的第一个维度代表的是bucket的编号,
  35:       那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
  36:       假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
  37:       那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
  38:       该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
  39:     ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
  40:     if (ha->keys_hash == NULL)
  41:     {
  42:         return NGX_ERROR;
  43:     }
  44:  
  45:     // 该数组在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
  46:     ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,sizeof(ngx_array_t) * ha->hsize);
  47:  
  48:     if (ha->dns_wc_head_hash == NULL)
  49:     {
  50:         return NGX_ERROR;
  51:     }
  52:  
  53:     // 该数组在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
  54:     ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
  55:     if (ha->dns_wc_tail_hash == NULL)
  56:     {
  57:         return NGX_ERROR;
  58:     }
  59:  
  60:     return NGX_OK;
  61: }

7. 向ngx_hash_keys_array中添加关键字

   1: ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
   2: {
   3:     ngx_uint_t  asize;
   4:  
   5:     if (type == NGX_HASH_SMALL)
   6:     {
   7:         asize = 4;
   8:         ha->hsize = 107;
   9:     }
  10:     else
  11:     {
  12:         asize = NGX_HASH_LARGE_ASIZE;
  13:         ha->hsize = NGX_HASH_LARGE_HSIZE;
  14:     }
  15:  
  16:     //初始化 存放非通配符关键字的数组
  17:     if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
  18:     {
  19:         return NGX_ERROR;
  20:     }
  21:  
  22:     //初始化 存放前置通配符处理好的关键字 数组
  23:     if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize, sizeof(ngx_hash_key_t)) != NGX_OK)
  24:     {
  25:         return NGX_ERROR;
  26:     }
  27:  
  28:     //初始化 存放后置通配符处理好的关键字 数组
  29:     if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))!= NGX_OK)
  30:     {
  31:         return NGX_ERROR;
  32:     }
  33:  
  34:     /*初始化 二位数组 ,这个数组存放的第一个维度代表的是bucket的编号,
  35:       那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
  36:       假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
  37:       那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
  38:       该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
  39:     ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
  40:     if (ha->keys_hash == NULL)
  41:     {
  42:         return NGX_ERROR;
  43:     }
  44:  
  45:     // 该数组在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
  46:     ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,sizeof(ngx_array_t) * ha->hsize);
  47:  
  48:     if (ha->dns_wc_head_hash == NULL)
  49:     {
  50:         return NGX_ERROR;
  51:     }
  52:  
  53:     // 该数组在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
  54:     ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
  55:     if (ha->dns_wc_tail_hash == NULL)
  56:     {
  57:         return NGX_ERROR;
  58:     }
  59:  
  60:     return NGX_OK;
  61: }

-

菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)[转]的更多相关文章

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

    菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

  2. 菜鸟nginx源码剖析数据结构篇(十) 自旋锁ngx_spinlock[转]

    菜鸟nginx源码剖析数据结构篇(十) 自旋锁ngx_spinlock Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

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

    菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn. ...

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

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

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

    菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

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

    菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blo ...

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

    菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

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

    菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

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

    菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

随机推荐

  1. Django开发学习BUG记录--RemovedInDjango19Warning:Model class apps.user.models.User doesn't declare an explicit app_label

    报错信息: /home/python/PycharmProjects/dailyfresh/apps/user/models.py:8: RemovedInDjango19Warning: Model ...

  2. assignment of day nine

    一.简述定义函数的三种方式 1.空函数:用于占位 2.有参函数:有参数的函数 3.无参函数:没有参数的函数 二.简述函数的返回值 1.如果函数没有返回值,默认返回None 2.函数可以通过return ...

  3. 007-Java可变个数形参重载【数组和...】

    重载方法时,可变个数形参的方法有两种方式 数组重载 ...重载 对两种方法,其实是一致的,示例如下: public class MethodArgsTest { //可变个数形参的格式:数据类型... ...

  4. 2019-8-31-dotnet-数组自动转基类数组提示-Co-variant-array-conversion-是什么问题

    title author date CreateTime categories dotnet 数组自动转基类数组提示 Co-variant array conversion 是什么问题 lindexi ...

  5. HttpWebRequest请求返回非200的时候 HttpWebResponse怎么接受返回错误提示

    当我们使用HttpWebRequest发送请求的时候如果服务器返回的不是200状态,那么请求代码肯定会异常,其实请求和返回并没有什么异常,只是.net内部就认定了 返回的不要是200 就是异常 那么我 ...

  6. Android开发 DialogFragment对话框详解

    前言 在聊DialogFragment之前,我们看看以往我们在Android里实现一个对话框一般有这几种方式: Dialog 继承重写Dialog实现一个自定义的Dialog AlertDialog ...

  7. canvas像素的操作

    ###在canvas中的像素操作 到目前为止,我们尚未深入了解Canvas画布真实像素的原理,事实上, 你可以直接通过ImageData对象操纵像素数据,直接读取或将数据数组写入该对象中 ###得到场 ...

  8. MySQL学习 EXISTS的用法 转载

    比如在Northwind数据库中有一个查询为 SELECT c.CustomerId,CompanyName FROM Customers c WHERE EXISTS( SELECT OrderID ...

  9. 揭秘 Flink 1.9 新架构,Blink Planner 你会用了吗?

    本文为 Apache Flink 新版本重大功能特性解读之 Flink SQL 系列文章的开篇,Flink SQL 系列文章由其核心贡献者们分享,涵盖基础知识.实践.调优.内部实现等各个方面,带你由浅 ...

  10. 玩转gulp之gulp编译less

    用好gulp grunt webpack让前端编程走向自动化,是作为一个前端开发必须学会的技能,不然逼格怎么提升的上去呢... 然后教大家如何用gulp装逼.一点点的学,都是相通的嘛 1. 安装nod ...