分享
 
 
 

Apache中的哈希表剖析(1)

王朝system·作者佚名  2006-03-10
窄屏简体版  字體: |||超大  

3.4 哈希表

3.4.1哈希表概述

作为线性数据结构,与前面所说的表格和队列等相比,哈希表无疑是查找速度比较快的一种。APR中较好的支持哈希表。APR中哈希表在文件apr_hash.h和apr_hash.c中实现。其数据结构定义如下:

struct apr_hash_t {?

apr_pool_t *pool;

apr_hash_entry_t **array;

apr_hash_index_t iterator; /* For apr_hash_first(NULL, ...) */

unsigned int count, max;

apr_hashfunc_t hash_func;

apr_hash_entry_t *free; /* List of recycled entries */

};

与其余的数据结构类似,第一个成员通常是分配该结构的内存池。哈希表中的每一个元素都用apr_hash_entry_t结构描述,array指向一个数组,该数组中每一个元素都是一个指向apr_hash_entry_t类型的链表。为了方便对哈希表的迭代循环遍历,每个结构中都维持一个apr_hash_index_t用以辅助迭代,该成员的详细细节我们在后面的部分会深入讨论。

count用以记录当前整个哈希表中结点的总数目。max则是当前哈希表中允许的哈希值的最大值,反映到结构中就是array数组中的元素的个数。

hash_func则是哈希函数,通过该函数可以确定给定元素在哈希表中的索引,可以使用默认的哈希函数,也可以使用自定义的哈希函数。

现在我们来看一下哈希表元素数据结构apr_hash_entry_t,该结构定义如下:

struct apr_hash_entry_t {

apr_hash_entry_t *next;

unsigned int hash;

const void *key;

apr_ssize_t klen;

const void *val;

};

hash是当前元素在哈希表中所对应的哈希索引值,key则是哈希项的键,value则是哈希项的值。哈希表中以键值key作为唯一的标识索引。如果键值存在冲突,那么这些冲突键将用链表关联起来。整个哈希表的结构可以用下面的图描述:

3.4.2哈希表创建

3.4.2.1 零创建

APR中创建一个哈希表可以通过两种途径:apr_hash_make和apr_hash_make_custom实现:

APR_DECLARE(apr_hash_t *) apr_hash_make(apr_pool_t *pool);

APR_DECLARE(apr_hash_t *) apr_hash_make_custom(apr_pool_t *pool, apr_hashfunc_t hash_func);

两者的区别就是哈希算法的不同。对于apr_hash_make而言,它的主要的工作就是创建apr_hash_t结构,并对其中的成员进行初始化,其中哈希元素的个数被初始化为16个,同时使用默认的哈希算法apr_hashfunc_default,而apr_hash_make_custom则使用自定义的哈希函数hash_func。

unsigned int apr_hashfunc_default(const char *char_key, apr_ssize_t *klen)

{

unsigned int hash = 0;

const unsigned char *key = (const unsigned char *)char_key;

const unsigned char *p;

apr_ssize_t i;

if (*klen == APR_HASH_KEY_STRING) {

for (p = key; *p; p++) {

hash = hash * 33 + *p;

}

*klen = p - key;

}

else {

for (p = key, i = *klen; i; i--, p++) {

hash = hash * 33 + *p;

}

}

return hash;

}

对于给定的键值key,apr_hashfunc_default返回它在哈希表中的索引。默认哈希算法采用了目前最为流行的times 33哈希算法,目前该算法被广泛使用在多个软件项目包括perl和巴克利(Berkeley DB)数据库中。对于字符串而言这是目前所知道的最好的哈希算法,原因在于该算法的速度非常快,而且分类非常好。

不过你不愿意使用该索引算法而希望使用自己的,那么你可以使用apr_hash_make_custom函数,它接受一个自定义的哈希算法函数,并将其赋值给apr_hash_t结构内的func成员,从而取代默认算法函数。

apr_hashfunc_t函数指针定义如下:

typedef unsigned int (*apr_hashfunc_t)(const char *key, apr_ssize_t *klen);

它需要两个参数,一个是需要进行哈希计算的键,另一个则是该键的长度。函数返回计算后的索引。

3.4.2.2 拷贝创建

与大部分数据结构一样,对于哈希表,APR也提供了拷贝创建方法,允许从一个已有的哈希表创建一个新的哈希表,拷贝函数声明如下:

APR_DECLARE(apr_hash_t *) apr_hash_copy(apr_pool_t *pool,const apr_hash_t *orig)

orig是源哈希表,在拷贝中所用所有的内存都来自内存池pool,拷贝后的哈希表由函数返回。

APR_DECLARE(apr_hash_t *) apr_hash_copy(apr_pool_t *pool,const apr_hash_t *orig)

{

apr_hash_t *ht;

apr_hash_entry_t *new_vals;

unsigned int i, j;

ht = apr_palloc(pool, sizeof(apr_hash_t) +

sizeof(*ht->array) * (orig->max + 1) +

sizeof(apr_hash_entry_t) * orig->count);

ht->pool = pool;

ht->free = NULL;

ht->count = orig->count;

ht->max = orig->max;

ht->hash_func = orig->hash_func;

ht->array = (apr_hash_entry_t **)((char *)ht + sizeof(apr_hash_t));

new_vals = (apr_hash_entry_t *)((char *)(ht) + sizeof(apr_hash_t) +

sizeof(*ht->array) * (orig->max + 1));

尽管称之为哈希表拷贝,但是apr_hash_copy实现的仅仅是一种影像拷贝。之所以称之为影像拷贝,是因为尽管拷贝后的哈希表能够实现与源哈希表相同的功能,但是内部数据组织已经发生了变化,最大的变化就是从源哈希表的不连续的链表结构转换为连续的块状结构。

既然是块状数据结构,我们首先就必须考虑块状结构的大小,然后才能分配。新的块状结构的大小应该与原有的链表结构大小相等。总的大小包括三方面:

1)、apr_hash_t结构的大小sizeof(apr_hash_t)

2)、apr_hash_t结构内array数组的大小,数组的总元素个数为max+1,每一个元素都是一个 apr_hash_entry_t类型的指针,因此整个数组的大小为(orig->max+1)*sizeof(*ht->array),或者也可以写成(orig->max+1)*sizeof(apr_hash_entry_t*)。

3)、整个哈希表中apr_hash_entry_t类型结点的总数,其值由orig->count决定,每个结点的大小为sizeof(apr_hash_entry_t),故总大小为sizeof(apr_hash_entry_t) * orig->count。

一旦确定了总的需要分配的内存大小,APR将从内存池中一次性分配足够的连续内存。这些内存将被分为三部分:apr_hash_t部分、max+1个apr_hash_entry_t指针部分以及count个哈希元素的大小。因此内存一旦分配完毕,除了使用原有哈希表结构成员初始化新的哈希表成员包括pool、count、max以及hash_func等之外,最重要的就是设置指针指向后面的两个内存部分,初始化后的布局如下所示:

1. j =0;

2. for (i = 0; i <= ht->max; i++) {

3. apr_hash_entry_t **new_entry = &(ht->array[i]); u

4. apr_hash_entry_t *orig_entry = orig->array[i];

5. while (orig_entry) {

6. *new_entry = &new_vals[j++];

7. (*new_entry)->hash = orig_entry->hash;

8. (*new_entry)->key = orig_entry->key;

9. (*new_entry)->klen = orig_entry->klen;

10. (*new_entry)->val = orig_entry->val;

11. new_entry = &((*new_entry)->next); v

12. orig_entry = orig_entry->next;w

13. }

14. *new_entry = NULL;

15. }

16. return ht;

在源哈希表中,ht->array数组中的每一个元素都是一个apr_hash_entry_t类型的指针,指向的是一个链表,而到了目标哈希表中,该指针指向的则是一个数组。因此,拷贝的一个重要任务就是调整array数组中的各个指针将其指向new_vals内存的适当的部分。调整后的布局如下图所示。

调整过程包括三个大步骤,图示中用jkl进行标识:

j array数组中的每一个元素都是一个指针,必须调整数组中的每一个指针指向new_vals部分的合适的位置。原则是:如果源哈希表中对应元素为NULL,则新指针也为NULL;如果对应的结点链表结点数目为n,则下一个索引指针与前一个偏移n*sizeof(apr_hash_entry_t)个。具体如灰色代码部分所示。

k 对每一个结点进行内容拷贝,如7.8.9.10行。

l 调整各个结点内部的next指针,指向它的直接后继结点,或者是紧靠它的下一个apr_hash_entry_t结点,或者是NULL。

new_entry = &((*new_entry)->next);

在上图中我们用虚线将链表结构和块状结构中对应得结点连接起来,以方便对比。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有