下面這個函數是基本散列表的初始化函數. 在http://blog.csdn.net/u012062760/article/details/48140449中也介紹的比較詳細了.
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
具體的函數內容這裏不再談了, 現在我們要關注的是這個函數的第二個參數, 它的結構體再貼出來:typedef struct {
ngx_str_t key;
ngx_uint_t key_hash; //由哈希函數根據key計算出的值. 將來此元素代表的結構體會被插入bucket[key_hash % size]
void *value;
} ngx_hash_key_t;
之前文章也說過了, 這個hash表是靜態只讀的,即不能在運行時動態添加新元素的,一切的結構和數據都在配置初始化的時候就已經規劃完畢. 那麼傳入的ngx_hash_key_t類型的names就包含了所有的散列表元素.那麼這些元素又從何而來呢?names中的元素是被動態添加進去的, 添加完畢後, 才最終調用ngx_hash_init函數構造散列表.下面就分析 names的由來.
ngx_hash_key_t數組的構成
上面介紹到, 這個key-value數組是動態生成的, 那麼必定有函數用來將新的key-value元素插入到這個動態數組中去.
Nginx選擇定義了下面這個結構體:
typedef struct {
ngx_uint_t hsize; //是指定keys_hash,dns_wc_head_hash,dns_wc_tail_hash的bucket個數用的
...
ngx_array_t keys; //初始化基本散列表用的動態數組. (基本散列表就是不含有通配符的元素)
//一個極其簡易的散列表, 它以數組的形式保存這hsize個元素, 每個元素都是ngx_array_t動態數組. 在動態添加元素的過程中, 會根據哈希函數生成的關鍵碼將用戶ngx_str_t類型的關鍵字添加到ngx_array_t的數組中. 這裏所有的用戶元素的關鍵字都是不帶通配符的
ngx_array_t *keys_hash;
...
} ngx_hash_keys_arrays_t;
上面這個結構體比較複雜, 當前我們只要關注其中的3個變量. 動態數組keys就是將來會用於ngx_hash_init函數第二個參數的數據. 它是以ngx_hash_key_t結構體作爲元素類型的動態數組(即ngx_array_t), 用於保存完全匹關鍵字. 與此同時, ngx_hash_keys_arrays_t結構體還建立了簡易散列表keys_hash, 作用是快速向keys動態數組容器插入元素.keys_hash是如何工作的呢?
如果沒有keys_hash這個簡易散列表, 在向keys動態數組添加元素時, 爲了避免出現相同關鍵字的元素, 每添加一個關鍵字元素都需要遍歷整個數組. 但是, 有了keys_hash這個簡易散列表後, 每當我們要向keys動態數組添加元素時, 就用這個元素的關鍵字計算出散列碼, 然後按照散列碼在keys_hash散列表中的相應位置建立ngx_array_t動態數組. 也就是說, keys_hash會是ngx_array_t類型的數組, 並且ngx_array_t本身就代表着數組, 不那麼確切的可以稱爲"二維數組". 每個ngx_array_t中的元素是ngx_str_t類型的, 它指向關鍵字字符串. 這樣, 再次添加同名關鍵字時, 就可以由散列碼立即獲得曾經添加的關鍵字, 以此來判斷其合法性或者選擇是否合併. 所以這個簡易散列表是爲了保證插入的速度以及插入元素的有效性.
處理ngx_hash_keys_arrays_t的操作
首先是初始化ngx_hash_keys_arrays_t結構體的操作.
初始化的函數主要就是初始化動態數組以及其需要的輔助用的簡單散列表
//此函數的第二個參數type指定keys_hash的ngx_array_t的個數
ngx_int_t
ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
{
ngx_uint_t asize;
//如果指定爲NGX_HASH_SMALL, 那麼keys_size的bucket個數即指定爲107; keys的動態數組大小初始化爲4
if (type == NGX_HASH_SMALL) {
asize = 4;
ha->hsize = 107;
} else {
//如果選擇更大的話, keys_hash的ngx_array_t個數爲10007; keys的動態數組大小初始化爲16384
asize = NGX_HASH_LARGE_ASIZE;
ha->hsize = NGX_HASH_LARGE_HSIZE;
}
if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
...
ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
if (ha->keys_hash == NULL) {
return NGX_ERROR;
}
...
return NGX_OK;
}
除了需要初始化之外, 另外一個處理函數就是往這個動態數組添加新的元素.
上面也提到了, 想要往動態數組keys中添加元素之前, 我們會先在預設的簡易散列表中查找該元素.
下面就是這部分內容的代碼實現, 該函數省略的大部分代碼, 那一部份是處理帶有通配符的關鍵字. 後面會將該段代碼補充進來.
ngx_int_t
ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,
ngx_uint_t flags)
{
size_t len;
u_char *p;
ngx_str_t *name;
ngx_uint_t i, k, n, skip, last;
ngx_array_t *keys, *hwc;
ngx_hash_key_t *hk;
last = key->len;
...
/* exact hash */
k = 0;
for (i = 0; i < last; i++) {
////如果沒有不允許修改關鍵字(修改其實意味着轉換關鍵字的大寫爲小寫)
if (!(flags & NGX_HASH_READONLY_KEY)) {
key->data[i] = ngx_tolower(key->data[i]);
}
//k的最終值即爲這個關鍵字字符串的哈希值(關鍵字碼)
k = ngx_hash(k, key->data[i]);
}
k %= ha->hsize;
/* check conflicts in exact hash */
//得到該關鍵字應該在的簡易散列表的bucket的ngx_array_t數組空間
//name指向該bucket的動態數組空間的首部地址
name = ha->keys_hash[k].elts;
//如果數組已經存在, 表明之前已經有元素被放在這個bucket了. 雖然不是說一定是完全相同的關鍵字, 但既然有這個可能就要進去找找
if (name) {
//現在的目標是找到有沒有與我這個新要插入的關鍵字字符串完全相同的存在
for (i = 0; i < ha->keys_hash[k].nelts; i++) {
//話說我怎麼經常發現Nginx會先比較該字符串的長度. 然後再選擇比較字符串的內容是否相同
if (last != name[i].len) {
continue;
}
if (ngx_strncmp(key->data, name[i].data, last) == 0) {
return NGX_BUSY;
}
}
} else {
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
//既然本來不存在, 那麼就在這個簡易散列表中插入這個新元素
//也不用擔心數組會放不下, 它是會自動擴容的, 所以才叫動態數組
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}
*name = *key;
//繼而在動態數組keys中插入這個元素
hk = ngx_array_push(&ha->keys);
if (hk == NULL) {
return NGX_ERROR;
}
hk->key = *key;
hk->key_hash = ngx_hash_key(key->data, last);
hk->value = value;
return NGX_OK;
...
}
構造一個基本散列表
現在, 既然知道了ngx_hash_key_t類型的動態數組構造, 下面就簡單的示範一下大致的調用過程:
目的是構造一個基本散列表:
ngx_str_t servername; //關鍵字(因爲其大小寫可能被更改, 所以需要分配空間)
servername.len = ngx_strlen("www.ben.com");
servername.data = ngx_pcalloc(pool, ngx_strlen("www.ben.com"));
ngx_memcpy(servername.date, "www.ben.com", ngx_strlen("www.ben.com"));
ngx_hash_t hash;
ngx_hash_init_t hash_init;
ngx_hash_keys_arrays_t ha;
ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
ngx_hash_keys_array_init(&ha, NGX_HASH_SMALL);
ngx_hash_add_key(&ha, &servername, 3); //填寫3是因爲我這裏不含通配符不需要特殊處理. 也沒有不允許更改大小寫的限制. 所以填寫非那兩個宏的值(那兩個分別是1和2)
hash_init.key = ngx_hash_key_lc;
hash_init.max_size = 100;
hash_init.bucket_size = 64;
hash_init.name = "balabala";
hash_init.pool = xxx->pool; //並不清楚從哪裏來的內存池...
if(ha.keys.nelts){ //keys裏有元素才需要初始化基本散列表
hash_init.hash = &hash;
hash_init.temp_pool = NULL;
ngx_hash_init(&hash_init, ha.keys.elts, ha.keys.nelts);
}
這樣, 一個基本散列表就初始化結束了.