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

Apache中的哈希表剖析(3)

阅读更多
转载请注明来源:http://blog.csdn.net/tingya
3.4.5哈希表合并
在Apache中经常需要将两个哈希表合并为一个新的哈希表,为此APR中提供了专门的哈希合并函数apr_hash_merge,该函数定义如下:
APR_DECLARE(apr_hash_t *) apr_hash_merge(apr_pool_t *p,
const apr_hash_t *h1,
const apr_hash_t *h2,
void * (*merger)(apr_pool_t *p,
const void *key,
apr_ssize_t klen,
const void *h1_val,
const void *h2_val,
const void *data),
const void *data);
h1和h2是两个需要进行合并的哈希表。由于某个键可能同时出现在h1和h2中,而合并后的哈希表中只允许存在一次,因此在新的合并后的哈希表中如何处理h1和h2中相同的键是必须考虑的事情。参数中的merge函数用来处理这种情况。data是传递个合并函数merger的额外参数。
{
apr_hash_t *res;
apr_hash_entry_t *new_vals = NULL;
apr_hash_entry_t *iter;
apr_hash_entry_t *ent;
unsigned int i,j,k;
res = apr_palloc(p, sizeof(apr_hash_t));
res->pool = p;
res->free = NULL;
res->hash_func = base->hash_func;
res->count = base->count;
res->max = (overlay->max > base->max) ? overlay->max : base->max;
if (base->count + overlay->count > res->max) {
res->max = res->max * 2 + 1;
}
res->array = alloc_array(res, res->max);
if (base->count + overlay->count) {
new_vals = apr_palloc(p, sizeof(apr_hash_entry_t) *
(base->count + overlay->count));
}
从大的角度而言,哈希表的合并非常简单:首先创建一个新的哈希表,然后将需要合并的哈希表填入到新哈希表中。创建新的哈希表可以分为三个步骤:
1)、创建apr_hash_t结构。这个可以通过apr_palloc实现,非常简单。
2)、初始化apr_hash_t内部的array数组。数组的大小取决于两方面:
■ 暂时将新哈希表的容量max设定为两个需要合并的哈希表中容量max最大的。
■ 如果现有的两个哈希表中已经使用的元素个数总和超过max,那么max个元素不足以存放两个哈希表中所有的元素,因此最终的容量调整为max*2 + 1。
3)、合并的两个哈希表中apr_hash_entry_t结点数目为bash->count + overlay->count,因此它们所占用的总内存大小为sizeof(apr_hash_entry_t)*(base->count+overlay->count)。合并前,两个哈希表中所有结点都是用链表方式保存,而在合并后所有的结点都用连续内存块保存,这点与前面的哈希表拷贝非常相似。
4)、一旦分配了需要的内存块,我们就必须调整array数组中的指针指向实际的内存。
j = 0;
for (k = 0; k <= base->max; k++) {
for (iter = base->array[k]; iter; iter = iter->next) {
i = iter->hash & res->max;
new_vals[j].klen = iter->klen;
new_vals[j].key = iter->key;
new_vals[j].val = iter->val;
new_vals[j].hash = iter->hash;
new_vals[j].next = res->array[i];
res->array[i] = &new_vals[j];
j++;
}
}
函数首先将第一个哈希表中的所有结点拷贝到上述的内存块中,同时调整array数组中的指针指向这些内存块。拷贝后的内存布局如下所示。
从下图中可以看出,对于源哈希表索引index链表,结点在链表中出现的顺序与在内存块中出现的顺序正好相反:源哈希中array[index]是链表中第一个结点的地址,而在目标哈希表中,array[index]则是链表中的最后一个结点的地址;
在第一次的拷贝中,另外一个重要的方面就是索引不变性。任意一个结点,如果在源哈希表中的索引为index,则它在新哈希表中的索引肯定也是index,这个可以下面的几行代码中看出:
i = iter->hash & res->max;
res->array[i] = &new_vals[j];
5)、当第一个哈希表拷贝到新哈希表后,对于第二个哈希表的处理就开始展开:
for (k = 0; k <= overlay->max; k++) {
for (iter = overlay->array[k]; iter; iter = iter->next) {
i = iter->hash & res->max ; u
for (ent = res->array[i]; ent; ent = ent->next) {
if ((ent->klen == iter->klen) &&
(memcmp(ent->key, iter->key, iter->klen) == 0)) {
if (merger) {
ent->val = (*merger)(p, iter->key, iter->klen,
iter->val, ent->val, data);
}
else {
ent->val = iter->val;
}
break;
}
}
if (!ent) {
new_vals[j].klen = iter->klen;
new_vals[j].key = iter->key;
new_vals[j].val = iter->val;
new_vals[j].hash = iter->hash;
new_vals[j].next = res->array[i];
res->array[i] = &new_vals[j];
res->count++;
j++;
}
}
}
与第一个哈希表的简单拷贝相比,第二个哈希表的处理无非也是将其中的每一个结点拷贝到新的哈希表中,因此最外层的循环为:
for (k = 0; k <= overlay->max; k++) {
for (iter = overlay->array[k]; iter; iter = iter->next) {
……
}
对于每一个结点,函数首先计算它的哈希值i,如u,并根据该哈希值i作出进一步的处理:
1)、如果经过第一次的拷贝之后,新哈希表中索引为i的链表仍然为NULL,那么该结点将直接插入索引i链表中。
2)、如果新哈希表中索引i链表已经存在,那么此时的处理又出现了进一步的分化:
■ 如果在索引i链表中存在一个与当前结点完全相同的结点(键长度相等,且键值完全相同),那么这种情况称之为键冲突,即哈希表1和哈希表2中存在完全相同的结点。对于这种情况通常的处理是由专门的合并函数merger完成,不过如果没有定义该合并函数,默认情况是否直接覆盖val。
■ 如果当前结点在索引i链表中不存在,那么此时意味着这是一个新结点,此时将其追加到索引i链表的尾部。比如哈希表2中的索引3链表,该链表中的结点最终插入到目标哈希表的10、11结点处。此时可以看到,10、11和第一个哈希表中的结点3不再连续,不过它们之间通过next关联在一起,如①所示。
最终合并后新哈希表的内存示意图如图所示。

3.4.6哈希表使用示例
下面的代码演示了哈希表的使用,包括下面几个方面:
1)、哈希表创建
2)、哈希表键/值增加
3)、哈希表键/值检索
4)、哈希表遍历
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <apr_general.h>
#include <apr_hash.h>
#include <apr_strings.h>
//在哈希表中增加、删除、修改元素
static void modify_hashtab(apr_hash_t *ht, apr_pool_t *mp)
{
apr_hash_set(ht, "foo", APR_HASH_KEY_STRING, "FOO");
apr_hash_set(ht, apr_pstrdup(mp, "bar"), APR_HASH_KEY_STRING, apr_pstrdup(mp, "BAR"));
apr_hash_set(ht, apr_pstrdup(mp, "foobar"), APR_HASH_KEY_STRING, apr_pstrdup(mp, "BAR"));
apr_hash_set(ht, apr_pstrdup(mp, "barfoo"), APR_HASH_KEY_STRING, apr_pstrdup(mp, "FOO"));
/* To delete an entry, pass NULL as a value */
apr_hash_set(ht, apr_pstrdup(mp, "to-del"), APR_HASH_KEY_STRING, apr_pstrdup(mp, "TO-DEL"));
apr_hash_set(ht, "to-del", APR_HASH_KEY_STRING, NULL);
apr_hash_set(ht,apr_pstrdup(mp,"override"),APR_HASH_KEY_STRING,apr_pstrdup(mp,"old-val"));
apr_hash_set(ht,apr_pstrdup(mp,"override"),APR_HASH_KEY_STRING,apr_pstrdup(mp, "new-val"));
}
//迭代整个哈希表
static void iterate_hashtab(apr_hash_t *ht)
{
apr_hash_index_t *hi;
for (hi = apr_hash_first(NULL, ht); hi; hi = apr_hash_next(hi)) {
const char *k;
const char *v;
apr_hash_this(hi, (const void**)&k, NULL, (void**)&v);
printf("ht iteration: key=%s, val=%s\n", k, v);
}
}
int main(int argc, const char *argv[])
{
apr_pool_t *mp;
apr_hash_t *ht;
apr_initialize();
apr_pool_create(&mp, NULL);
ht = apr_hash_make(mp);
modify_hashtab(ht, mp);
{
const char *val = apr_hash_get(ht, "foo", APR_HASH_KEY_STRING);
printf("val for \"foo\" is %s\n", val);
}
iterate_hashtab(ht);
apr_pool_destroy(mp);
apr_terminate();
return 0;
}
程序运行结果如下:



关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行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. 拥有...

    nosql 入门教程

    第12章 使用Hive分析大数据 199 12.1 Hive基础 199 12.2 回到电影评分 203 12.3 亲切的SQL 209 12.4 HiveQL连接 211 12.4.1 计划解释 213 12.4.2 分区表 215 12.5 小结 215 第13章 综览数据库内部 216 13.1...

    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. 拥有...

    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上百实例源码以及开源项目源代码

     Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...

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

     Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...

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

    分别是XML快速入门,XML的概念,XML的术语,XML的实现,XML的实例分析。最后附录介绍了XML的相关资源。作者站在普通网页设计人员的角度,用平实生动的语言,向您讲述XML的方方面面,帮助你拨开XML的神秘面纱,快速...

Global site tag (gtag.js) - Google Analytics