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.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 3.6.4. 拥有...
第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...
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. 拥有...
3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 ...
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. 在批处理...
在同一个数据库中创建多个表的缺陷 7.5. 优化MySQL服务器 7.5.1. 系统因素和启动参数的调节 7.5.2. 调节服务器参数 7.5.3. 控制查询优化器的性能 7.5.4. 编译和链接怎样影响MySQL的速度 7.5.5. ...
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. 拥有...
3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行 3.6.3. 列的最大值:按组 ...
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. 拥有...
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 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...
分别是XML快速入门,XML的概念,XML的术语,XML的实现,XML的实例分析。最后附录介绍了XML的相关资源。作者站在普通网页设计人员的角度,用平实生动的语言,向您讲述XML的方方面面,帮助你拨开XML的神秘面纱,快速...