學習openssl中的lhash,源碼在crypto\lhash目錄下。Lhash在openssl中用到的地方很多,如文本數據庫txt_db中,具體的以後再分析。首先看看lhash的實現結構,lhash的實現很多地方和stack的實現相同。參考了趙春平老師的Openssl編程和mm350670610的這篇博文。
//哈希節點的定義,是一個單鏈表
typedef struct lhash_node_st
{
void *data;//存放數據的地址
struct lhash_node_st *next;//指向下一個地址
#ifndef OPENSSL_NO_HASH_COMP
unsigned long hash;//哈希值
#endif
} LHASH_NODE;
//哈希表的定義
typedef struct lhash_st
{
LHASH_NODE **b;//爲一個指針數據,指向一個單鏈表
LHASH_COMP_FN_TYPE comp;//比較函數指針
LHASH_HASH_FN_TYPE hash;//哈希函數指針
unsigned int num_nodes;//鏈表個數
unsigned int num_alloc_nodes;//b分配空間的大小,已申請的個數,大於等於num_nodes
unsigned int p;
unsigned int pmax;//p和pmax是爲了優化性能能設置的參數
unsigned long up_load; /* load times 256 */
unsigned long down_load; /* load times 256 */
unsigned long num_items;//表中所有數據的個數
//下面是一些計數器
unsigned long num_expands;
unsigned long num_expand_reallocs;
unsigned long num_contracts;
unsigned long num_contract_reallocs;
unsigned long num_hash_calls;
unsigned long num_comp_calls;
unsigned long num_insert;
unsigned long num_replace;
unsigned long num_delete;
unsigned long num_no_delete;
unsigned long num_retrieve;
unsigned long num_retrieve_miss;
unsigned long num_hash_comps;
int error;
} LHASH;
由上面的兩個結構可以知道openssl中實現的哈希表,在處理衝突中使用的拉鍊法。拉鍊是解決衝突的一種行之有效的方法。解決的方法是爲每個哈希地址建立一個單鏈表。表中存儲所有具有該哈希值相同的值。該結構的基本結構如下:
先講一下p和pmax的用處,當哈希表已經很滿的時候,發生衝突的概率更大,訪問數據時要把整個鏈表遍歷一遍,這樣性能會下降,openssl中的lhash採用的是,是將hash表的數據增長一些,把那些鏈表的元素忘新增的部分移動一些,這樣就能才產生很好的效果。如何判斷哈希表是否很慢呢。通過使用裝填因子來表示:
裝填因子(a)=num_items/num_nodes;
在添加數據時會進行判斷(在lh_insert函數中):
//如果裝填因子超過上限,則拓展哈希表
if (lh->up_load <= (lh->num_items*LH_LOAD_MULT/lh->num_nodes))
expand(lh);
在刪除數據時也同樣進行判斷(在lh_delete函數中):
if((lh->num_nodes > MIN_NODES) &&
(lh->down_load>= (lh->num_items*LH_LOAD_MULT/lh->num_nodes)))
contract(lh);//contract和expand函數功能相反。
其中LH_LOAD_MULT=256,up_load=2*256,down_load=256
下面具體分析一下expand函數:
static void expand(LHASH *lh)
{
LHASH_NODE **n,**n1,**n2,*np;
unsigned int p,i,j;
unsigned long hash,nni;
//將計算都加1
lh->num_nodes++;
lh->num_expands++;
p=(int)lh->p++;
n1= &(lh->b[p]);
n2= &(lh->b[p+(int)lh->pmax]);
*n2=NULL; /* 27/07/92 - eay - undefined pointer bug */
nni=lh->num_alloc_nodes;
for (np= *n1; np != NULL; )
{
#ifndef OPENSSL_NO_HASH_COMP
hash=np->hash;
#else
hash=lh->hash(np->data);
lh->num_hash_calls++;
#endif
if ((hash%nni) != p)//如果!-p則將其移動pmax的位置
{ /* move it */
*n1= (*n1)->next;①
np->next= *n2;②
*n2=np;③
}
else//將指針後移一個位置
n1= &((*n1)->next);
np= *n1;
}
if ((lh->p) >= lh->pmax)//當存儲空間不夠,擴大存儲空間
{
…………
…………
}
}
通常申請的空間是使用空間的2倍,即num_alloc_nodes=2*num_nodes;我們可以通過查看lh_new函數中的初始化部分。其中宏定義MIN_NODES=16
ret->num_nodes=MIN_NODES/2;
ret->num_alloc_nodes=MIN_NODES;
ret->p=0;
ret->pmax=MIN_NODES/2;
開始,鏈表使用b[0]~b[7],當需要拓展的時候,使用到b[8~15]已申請的空間。當裝填因子沒達到上限時,所在數組下標的位置由nn=hash%lh->pmax;(在getrn函數中)得到,而裝填因子達到上限時由nni=lh->num_alloc_nodes;(expand函數中)決定下標的位置,此時需要將下標爲p的指向鏈表的部分元素移動到pmax下標的鏈表。首先遍歷p下標指向的鏈表,通過執行nni=lh->num_alloc_nodes,判斷值是否爲p,若爲p則跳過,否則將其移動到pmax下標的鏈表,直接插入到pmax下標指向鏈表的表頭。具體移動如下圖所示: