菜鳥nginx源碼剖析數據結構篇(七) 哈希表 ngx_hash_t(下)[轉]

菜鳥nginx源碼剖析數據結構篇(七) 哈希表 ngx_hash_t(下)nginx

 

  • Author:Echo Chen(陳斌)數組

  • Email:chenb19870707@gmail.com數據結構

  • Blog:Blog.csdn.net/chen19870707app

  • Date:Nov 3rd, 2014函數

    在前面一篇文章《菜鳥nginx源碼剖析數據結構篇(六) 哈希表 ngx_hash_t(上)》 裏介紹了 普通哈希表 和 帶有通配符的哈希表 的基本結構和初始化方法,因爲篇幅的緣由未能解析完結,這篇繼續解源碼中剩餘的部分。ui

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

    普通哈希表的查找比較簡單,思想就是先根據hash值找到對應桶,而後遍歷這個桶的每個元素,逐字匹配是否關鍵字徹底相同,徹底相同則找到,不然繼續,直至找到這個桶的結尾(value = NULL)。spa

       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位來攜帶信息嗎?其是有特殊意義的,以下圖所示.net

  • 00 - value 是 "example.com" 和 "*.example.com"的數據指針
  • 01 - value 僅僅是 "*.example.com"的數據指針
  • 10 - value 是 支持通配符哈希表是 "example.com" 和 "*.example.com" 指針
  • 11 - value 僅僅是 "*.example.com"的指針 

    221627_1k2x_866208

查找的思路就是根據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表的指針的時候,查詢過程結束。

 

221627_1k2x_866208

 

理解了這個,咱們就能夠看源代碼了,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: }

-

相關文章
相關標籤/搜索