Apache中的哈希表剖析1-3

转载请注明来源: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_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内存的适当的部分。调整后的布局如下图所示。
调整过程包括三个大步骤,图示中用 j k l 进行标识:
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);

在上图中我们用虚线将链表结构和块状结构中对应得结点连接起来,以方便对比。
3.4.3数据插入和获取
对于哈希表而言,一个重要的任务就是插入key/value数据以及根据键值获取相应的值。APR中定义了函数apr_hash_set和apr_hash_get分别实现上面的功能。
首先我们来看apr_hash_get函数,该函数需要三个参数,分别用以描述操作的哈希表,键值以及键的长度。
APR_DECLARE(void *) apr_hash_get(apr_hash_t *ht,const void *key,apr_ssize_t klen)
{
apr_hash_entry_t *he;
he = *find_entry(ht, key, klen, NULL);
if (he)
return (void *)he->val;
else
return NULL;
}
从上面的函数可以看到,通过键值查找对应的哈希元素的过程真正的工作是由find_entry完成的,尽管find_entry是一个内部函数,不过它是很多函数的基础,其定义如下:
static apr_hash_entry_t **find_entry(apr_hash_t *ht,const void *key,apr_ssize_t klen,
const void *val)
{
apr_hash_entry_t **hep, *he;
unsigned int hash;
hash = ht->hash_func(key, &klen);
for (hep = &ht->array[hash & ht->max], he = *hep;
he; hep = &he->next, he = *hep) {
if (he->hash == hash
&& he->klen == klen
&& memcmp(he->key, key, klen) == 0)
break;
}
if (he || !val)
return hep;
/* add a new entry for non-NULL values */
if ((he = ht->free) != NULL)
ht->free = he->next;
else
he = apr_palloc(ht->pool, sizeof(*he));
he->next = NULL;
he->hash = hash;
he->key = key;
he->klen = klen;
he->val = val;
*hep = he;
ht->count++;
return hep;
}
整个函数的处理过程可以分为三大部分:
1)、根据键值key计算出它在整个表array中的索引值hash,这个过程非常的简单,通常可以直接调用apr_hash_t内部的hash_func计算获取
2)、正如前面所说,由于哈希值冲突的存在,因此存在多个键值对应同一个索引的情况,所有索引计算结果相同的键都保存在以array[hash]为首地址的链表中。因此对于在确定hash索引之后,下一步必须处理的就是遍历该索引对应的链表,在其中查找是否存在目标结点,当一个结点必须满足下面的条件的时候才能称之为完全匹配:
■ 当前结点的hash值与计算出的哈希值相等。一般情况下,这个条件都是满足的,因为不相等的话不可能挂接到hash索引对应的链表。
■ 键值长度完全相同
■ 键的值完全满足条件
3)、尽管函数的名称为find_entry,这个名字容易让人误会为函数仅仅完成查找功能,事实上并不如此。函数apr_hash_get和apr_hash_set内部都调用了该函数。因此,find_entry并不仅仅是查找,同时还具备增加结点的功能。至于到底是查找还是增加由最后一个参数val决定,如果val为NULL,则函数理所当然为查找,否则为添加。
对于查找功能,函数直接返回找到的元素结点。如果是增加,则将当前结点增加到hash索引链表的最末尾。
3.4.4哈希表迭代遍历
APR中哈希表的遍历遵循的一个原则就是深度遍历原则,按照这种思路,遍历过程首先将hash=0的链表中的所有结点遍历完毕,而后继续遍历hash=1的链表,直至hash=max的链表,这种思路可以用下面的图示描述,红虚线就是哈希表元素的遍历顺序:

在讲解APR中遍历算法之前,我们首先用最普通的方法来实现对哈希表的遍历,下面是遍历的框架代码:
int i=0;
for(;i<max;i++)
{
apr_hash_entry_t *ht;
if(array[i]==NULL)
continue;
ht=array[i];
while(ht!=NULL)
{
//处理该结点,或者打印或者其余操作
DoSomethingAboutTheNode();
ht=ht->next;
}
}
上面的程序基本能够实现对整个哈希表的遍历,而且也很容易理解。但是如果从使用者的角度去考虑一下就很容易发现尽管上面的遍历没有问题,但是对用户而言则并不是很现实。使用上面的遍历算法的一个前提就是用户必须能够知道哈希表的内部实现结构,事实上这个对大部分的用户并不现实,而且哈希表的内部结构通常总是对用户屏蔽的。如果这样,问题又出来了,既然用户不需要了解哈希表的内部实现细节,那么他又从何知道使用上面的遍历代码呢??
在C++ STL中大部分的容器,比如vector,stack,list以及queue等等,它们内部都支持一种称之为迭代子的类型,该类型允许将容器类型和算法分开来,彼此独立设计,最后再通过胶合剂将它们关联起来,不过这种关联是一种松耦合关系,下面是使用向量(vector)迭代子对向量进行迭代的过程:
vector<int> coll;
……
vector<int>::iterator pos;
for (pos=coll.begin(); pos<coll.end(); ++pos) {
printf(“%d ”,*pos);// cout << *pos << ' ';
}
从上面的代码中我们可以看到,对于用户而言根本不需要知道vector的内部细节,它需要知道的仅仅是从begin()访问到end(),至于begin()和end()返回的是不是向量中真正的第一个和最后一个,那不是用户需要考虑的事情。
不过迭代子是范型技术的产物,是和C++紧密联系的,而Apache则是纯C编写的,因此当然不可能直接使用STL,不过它显然是受了STL的影响——它的目的就是使用C语言仿造一个哈希表上的迭代子,使得算法与细节分开。下面是APR中使用仿迭代子对哈希表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);
}
在上面的代码中你根本就看不出哈希表的内部细节。这实际上也达到了我们前面提出的要求。
为了实现仿造的效果,哈希表中引入了一个辅助数据结构apr_hash_index_t,用于记录当前访问的结点的相关索引信息:
struct apr_hash_index_t {
apr_hash_t *ht;
apr_hash_entry_t *this, *next;
unsigned int index;
};
ht是当前访问的哈希表,index是当前访问的结点所在的哈希索引,值从0到max之间。this和next是这个结构的最重要的成员。this用以指向当前正在访问的结点,而next则指向按照深度遍历时this之后下一个将要被访问的结点。在整个遍历过程中,最重要的就是不断地调整this和next的指针,下面是可能出现的情况:
在分析上面的图示的时候,我们给出两个定义:直接后继结点间接后继结点
对于某个结点,它的直接后继结点就是可以通过next指针直接获取的结点。比如上图的(1)-(4),this结点的直接后继结点都是NULL,而(5)中,this的直接后继存在,且为next。

对于某个结点,它的间接后继结点就是按照前述的哈希表深度遍历访问顺序中的下一个结点。对于图(5),this的间接后继结点就是它的直接后继结点。下表给出了上图中的各个直接后继和间接后继结点的情况:
直接后继结点
间接后继结点
(1)
NULL
NULL
(2)
NULL
Not NULL
(3)
NULL
NULL
(4)
NULL
Not NULL
(5)
Not NULL
Not NULL
从上表可以看出(1)-(4)图可以归结为一个大类,那就是this的直接后继都是为NULL,这导致this结点和next结点位于不同的索引链表中,而(5)则是另外一个大类,它的直接后继不为NULL,这导致this和next位于同一链表中。对于给定的this结点,如何获取它的下一个结点??Apache中使用apr_hash_next函数来获取this结点的下一个结点:
APR_DECLARE(apr_hash_index_t *) apr_hash_next(apr_hash_index_t *hi)
{
hi->this = hi->next;
while (!hi->this) {
if (hi->index > hi->ht->max)
return NULL;
hi->this = hi->ht->array[hi->index++];
}
hi->next = hi->this->next;
return hi;
}
this结点由传入参数hi指定,函数内部计算this的next结点,并继续通过hi返回出去。函数中正是利用了上面所说的分类规律:
■ while循环用于处理上图的(1)(2)(3)(4)四种情况,如果直接后继为NULL,则直接跳至下一个索引链表工作,直到索引为max为止。
■ hi->next=hi->this->next用以处理直接后继不为NULL的情况,那么此时只需要调整next就可以了。
除了apr_hash_next之外,Apache中还提供了另外两个辅助函数apr_hash_first和ap_hash_this。apr_hash_first主要为遍历整个哈希表作必要的准备,并返回整个哈希表的第一个元素的索引结构apr_hash_index_t。该函数需要两个参数:内部索引分配所需要的内存池以及需要遍历的哈希表:
APR_DECLARE(apr_hash_index_t *) apr_hash_first(apr_pool_t *p, apr_hash_t *ht)
{
apr_hash_index_t *hi;
if (p)
hi = apr_palloc(p, sizeof(*hi));
else
hi = &ht->iterator;
hi->ht = ht;
hi->index = 0;
hi->this = NULL;
hi->next = NULL;
return apr_hash_next(hi);
}
函数的前半部分主要进行apr_hash_index_t结构的初始化工作,如果内部具有迭代子,那么直接使用该迭代子,否则创建新的迭代子,并使用该迭代子进行第一次迭代,apr_hash_next将返回哈希表中的第一个元素,同时下一个间接后继结点同时也返回。
apr_hash_this函数主要返回给定结点的各种信息,通常情况下,总是将this指针传递个该函数:
APR_DECLARE(void) apr_hash_this(apr_hash_index_t *hi,const void **key,
apr_ssize_t *klen,void **val)
{
if (key) *key = hi->this->key;
if (klen) *klen = hi->this->klen;
if (val) *val = (void *)hi->this->val;
}
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;
}
程序运行结果如下:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值