`
liuzhaomin
  • 浏览: 198566 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Apache中的哈希表剖析(1)

阅读更多
转载请注明来源:http://blog.csdn.net/tingya
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_tklen;
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);

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

关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish1234 at sina.com.cn与之联系!

如果你觉得本文不错,请点击文后的“推荐本文”链接!!
分享到:
评论

相关推荐

    MySQL 5.1中文手冊

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 3.6.4. 拥有...

    mysql官方中文参考手册

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 3.6.4. 拥有...

    nosql 入门教程

    第1章 NoSQL的概念及适用范围 2 1.1 定义和介绍 3 1.1.1 背景与历史 3 1.1.2 大数据 5 1.1.3 可扩展性 7 1.1.4 MapReduce 8 1.2 面向列的有序存储 9 1.3 键/值存储 11 1.4 文档数据库 14 1.5 图形数据库 ...

    MYSQL中文手册

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 ...

    MySQL 5.1官方简体中文参考手册

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter/(第 3/24 页)2006-11-02 19:12:13 MySQL 5.1 Reference Manual 3.5. 在批处理...

    mysql5.1中文手册

    在同一个数据库中创建多个表的缺陷 7.5. 优化MySQL服务器 7.5.1. 系统因素和启动参数的调节 7.5.2. 调节服务器参数 7.5.3. 控制查询优化器的性能 7.5.4. 编译和链接怎样影响MySQL的速度 7.5.5. ...

    MySQL 5.1参考手册 (中文版)

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 3.6.4. 拥有...

    MySQL 5.1参考手册中文版

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 ...

    MySQL 5.1参考手册

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 3.6.4. 拥有...

    MySQL5.1参考手册官方简体中文版

    3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 3.6.4. 拥有...

    JAVA上百实例源码以及开源项目源代码

     [MonthMaker.java] 月份表算法类  [Pallet.java] 调色板,统一配色类 Java扫雷源码 Java生成自定义控件源代码 2个目标文件 Java实现HTTP连接与浏览,Java源码下载 1个目标文件 摘要:Java源码,网络相关,HTTP ...

    JAVA上百实例源码以及开源项目

     [MonthMaker.java] 月份表算法类  [Pallet.java] 调色板,统一配色类 Java扫雷源码 Java生成自定义控件源代码 2个目标文件 Java实现HTTP连接与浏览,Java源码下载 1个目标文件 摘要:Java源码,网络相关,HTTP ...

    XML轻松学习手册--XML肯定是未来的发展趋势,不论是网页设计师还是网络程序员,都应该及时学习和了解

    1.名字中可以包含字母、数字以及其它字母; 2.名字不能以数字或"_" (下划线) 开头; 3.名字不能以字母 xml (或 XML 或 Xml ..) 开头; 4.名字中不能包含空格。 在XML文档中任何的差错,都会得到同一个结果:...

Global site tag (gtag.js) - Google Analytics