c语言键值配对——(key, value)
看一个C++项目时,其中解析配置文的部分引发了我的思考。
配置文件问普通字符文件,内容都是类似 如下:
ipaddr=127.0.0.1
port=888
logfile=log
C++对此配置文件解析字符,按每次处理一行,以”=”作为分隔符将每行分成两个字符串作为(key,value)插入map 变量,举个例子
以ipaddr=127.0.0.1为例:
std::map<std::string, std::string> config_map;
//key,value = “ipaddr","127.0.0.1"
config_map..insert(std::make_pair("ipaddr", "127.0.0.1");
这个提取配置信息作为键值对 在C++中很容易实现,但是在C项目中又该如何实现呢,好像没有直接以字符串作为索引的数组,实现方式需要特殊处理。
我想到的是哈希表,这个东西类似于数组,但是索引值是以 字符串计算而来,关于哈希值得计算公式其实很简单,就是对字符串进行一系列操作。
网上有个网友写了一篇很不错的关于哈希表的文章:
https://blog.csdn.net/txl199106/article/details/40184965
其中对于哈希表的链地址法实现Hash写的非常好,链地址能在一定程度上对键冲突进行处理,如果键冲突太频繁,获取就要考虑另一种方式实现了,但是对于今天的主题,链地址hash表已经足够,并且简单。
现附上那位网友的源码,后面我会稍微修改一下,来演示键值对冲突:
代码来源
https://blog.csdn.net/txl199106/article/details/40184965
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
char *name;
char *desc;
struct _node *next;
}node;
#define HASHSIZE 101
static node* hashtab[HASHSIZE];
void inithashtab(){
int i;
for(i=0;i<HASHSIZE;i++)
hashtab[i]=NULL;
}
unsigned int hash(char *s){
unsigned int h=0;
for(;*s;s++)
h=*s+h*31;
return h%HASHSIZE;
}
node* lookup(char *n){
unsigned int hi=hash(n);
node* np=hashtab[hi];
for(;np!=NULL;np=np->next){
if(!strcmp(np->name,n))
return np;
}
return NULL;
}
char* m_strdup(char *o){
int l=strlen(o)+1;
char *ns=(char*)malloc(l*sizeof(char));
strcpy(ns,o);
if(ns==NULL)
return NULL;
else
return ns;
}
char* get(char* name){
node* n=lookup(name);
if(n==NULL)
return NULL;
else
return n->desc;
}
int install(char* name,char* desc){
unsigned int hi;
node* np;
if((np=lookup(name))==NULL){
hi=hash(name);
np=(node*)malloc(sizeof(node));
if(np==NULL)
return 0;
np->name=m_strdup(name);
if(np->name==NULL) return 0;
np->next=hashtab[hi];
hashtab[hi]=np;
}
else
free(np->desc);
np->desc=m_strdup(desc);
if(np->desc==NULL) return 0;
return 1;
}
/* A pretty useless but good debugging function,
which simply displays the hashtable in (key.value) pairs
*/
void displaytable(){
int i;
node *t;
for(i=0;i<HASHSIZE;i++){
if(hashtab[i]==NULL)
printf("()");
else{
t=hashtab[i];
printf("(");
for(;t!=NULL;t=t->next)
printf("(%s.%s) ",t->name,t->desc);
printf(".)\n");
}
}
}
void cleanup(){
int i;
node *np,*t;
for(i=0;i<HASHSIZE;i++){
if(hashtab[i]!=NULL){
np=hashtab[i];
while(np!=NULL){
t=np->next;
free(np->name);
free(np->desc);
free(np);
np=t;
}
}
}
}
main(){
int i;
char* names[]={"name","address","phone","k101","k110"};
char* descs[]={"Sourav","Sinagor","26300788","Value1","Value2"};
inithashtab();
//增加部分--------------------------打印hash_table_index
for(i = 0; i < 5; i++)
{
unsigned int hi=hash(names[i]);
printf("%s--hash_table_index=%d\n", names[i], hi);
}
//---------------------------------------打印hash_table_index
for(i=0;i<5;i++)
install(names[i],descs[i]);
printf("Done\n");
printf("If we didnt do anything wrong..""we should see %s\n",get("k110"));
install("phone","9433120451");
printf("Again if we go right, we have %s and %s\n",get("k101"),get("phone"));
displaytable();
cleanup();
return 0;
}
在此代码中,我添加了打印 字符串 对应的hash值
//增加部分--------------------------打印hash_table_index
for(i = 0; i < 5; i++)
{
unsigned int hi=hash(names[i]);
printf("%s--hash_table_index=%d\n", names[i], hi);
}
//---------------------------------------打印hash_table_index
输出信息
可以看见,不同字符串获得hash值不一样,目前还没有键冲突,那么每个字符串在哈希表(static node* hashtab[HASHSIZE])中的索引便知道了, 打印信息很直白,结合源码很容易看懂。
下面来模拟键冲突的情况,其实要想两个不同的字符串对应的hash值相同,即键冲突,这样的两个字符串很难找到,不妨修改hash值的计算方式,给定一个固定值,修改如下代码:
unsigned int hash(char *s){
unsigned int h=0;
/**
for(;*s;s++)
h=*s+h*31;
**/
return h%HASHSIZE;
}
让其获得的hash值都为0,所以不管怎样都会出现hash值相同,即键冲突
输出信息如下:
所有的键值对都在hash表的第一项中,冲突键使用单链表连接起来了,具体是在插入新字符串键时先查找已有hash表中是否存在,如果键相等再比较字符串是否相等,见下:
node* lookup(char *n){
unsigned int hi=hash(n);
node* np=hashtab[hi];
for(;np!=NULL;np=np->next){
if(!strcmp(np->name,n))
return np;
}
return NULL;
}
如果字符串相等,返回已有的键值对,覆盖对应的值,如果查找不存在,申请新的空间,从链表头部插入新键值对。
在C语言中链式哈希表能实现类似 C++ 中map 的键值对功能,有这个需求的可以参考以上源码内容。