openssl學習之lhash

學習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下標指向鏈表的表頭。具體移動如下圖所示:





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章