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下标指向链表的表头。具体移动如下图所示:





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值