c语言实现分析hashtable算法

本文介绍了一种针对C语言设计的HashTable实现方案,该方案适用于处理内置基础类型数据和字符串数据,支持按照插入顺序进行遍历。

从语言内容来讲,C绝对是足够精炼的,它提供且仅提供了我们工作所必须的编程元素。

从可以实现的功能以及能为我们提供的代码管理和性能支持上来看,它也做的恰到好处。

没有C++的繁琐、比脚本及所谓的OO语言更高效、当然也比汇编更容易理解。

 

不过对于用惯了Java的HashMap、LinkedHashMap,Python的Dict,以及PHP的Array 的同学来说,C的简洁似乎就有些简陋,甚至是蹩脚了。

 

最近项目中需要在C语言中使用HashTable来提高按键的查找速度,在网上找了很多现成的实现,发现写的都很随意,都有问题。很多现有的实现版本中都是使用char*作为key,void*作为值,这种做法最简单,但内存效率和计算效率都不高。

这种方案面临一个问题,就是:HashTable是否要申请新的内存空间来保存key和value的值,而不是只记录指针的值。

如果不保存,则指针指向的内存区可能会被其他代码销毁,则内容会丢失,程序会失败。

如果保存,就需要进行频繁的内存申请和销毁,尤其是当键或值是语言内置的基础类型(比如char、int、long、short、float、double)时,存放值的内存大小比存放指针值的内存还小。会导致更多的小块内存的操作和内存碎片。

为了能够让C更好地为我们的工作服务,我决定自己搞一个HashTable。

 

具体需求:

  1. 在我的项目中,很多情况下HashTable的key和value都是内置基础类型(如int、double),字符串的情况也比较多,其他的复杂情况极少。即我们的HashTable更多的是处理内置类型数据或者字符串数据。
  2. 需要支持对key、value按照插入顺序进行遍历。

我们的方案:

  1. 考虑到内置类型的size最大只有8Byte,而且所有指针本身的大小也是8Byte(64bit 的机器),因此我们只需要一个8Byte的空间来存错所有的基础类型的值,或者指针(一般是char*)。这样当面对基础类型的时候,不需要malloc额外的空间来存储,在遇到字符串类型(char*)的数据时,使用malloc申请内存空间存储字符串内容,并将指针存在这个8Byte的空间中。
  2. 同时HashTable要维护当前key和value的类型是什么,需要在插入数据和查找数据时根据key和value的类型做对应的类型转换。
  3. key和value都支持有限的类型:key的类型只支持int、long、char*;value的类型支持char、short、int、long、float、double、char*。
  4. 至于按照插入顺序进行遍历,则只需要对插入的每个元素维护一个全局的指针域即可,这个可以参考Java中LinkedHashMap的实现。

数据结构设计:

 

考虑到上述情况,我们对HashTable的结构设计如下:

 

C代码   收藏代码
  1. #define VLEN       8  
  2. #define TNLEN     32  
  3.   
  4.   
  5. typedef unsigned long ulong;  
  6. typedef unsigned int  uint;  
  7.   
  8. typedef struct _bucket {  
  9.     ulong h;          /* hash value of key, keyvalue if key is a uint or ulong */  
  10.     char * key;       /* the point to key , if key is a string */  
  11.     char value[VLEN]; /* store a var of builtin type in a 8Byte buffer */  
  12.     struct _bucket *pListNext;  
  13.     struct _bucket *pListLast;  
  14.     struct _bucket *pNext;  
  15.     struct _bucket *pLast;  
  16. } Bucket;  
  17.   
  18. typedef struct _hashtable{  
  19.     int nTableSize;  
  20.     int nTableMask;  
  21.     int nNumOfElements;  
  22.     char keyType[TNLEN];     /* can be "int","long","char*" */  
  23.     char valueType[TNLEN];   /* can be "char","short","int","long","float","double","char*" */  
  24.     Bucket * pInternalPointer;  
  25.     Bucket * pListHead;  
  26.     Bucket * pListTail;  
  27.     Bucket ** arBuckets;  
  28. } HashTable;  
 

  

内存结构:

 

假设我们创建一个size(桶数)为6的HashTable,并且尝试插入4个元素,其中第一个元素和第四个元素hash冲突,第二个元素与第三个元素hash冲突。那么按照设计,该HashTable在内存中的结构如下图所示:
 
在按键查找时,先通过计算hash值,并计算hash值对应的桶的索引[0,6),然后按照蓝色箭头pNext(指针)的指向即可找到对应的元素(或者找不到)。
在按照插入顺序遍历时,从head指针开始,按照墨色箭头pListNext(指针) 的指向即可完成元素的遍历。
 
接口需求:
我们希望这个HashTable能够支持多种数据类型,而且在使用的时候尽可能的方便。
用户在创建HashTable的实例时指定key和value的类型,在进行增、删、改、查以及遍历操作时直接使用对应的类型操作即可。
假设用户系统通过如下方式访问该HashTable:
C代码   收藏代码
  1. /*创建HashTable实例*/  
  2. HashTable * ht = create_hashtable(100,char*,double);  /*key:char*,value:double*/  
  3.   
  4. /*插入元素"xiaoqiang" => 1234.567 */  
  5. hash_add("xiaoqiang",1234.567);   
  6.   
  7. /*插入元素"helloworld" => 234567.891 */   
  8. hash_add("helloworld",234567.891);    
  9.   
  10. /*遍历元素*/   
  11. char * key = NULL;    
  12. double value = 0.0;    
  13. for (reset(ht);isnotend(ht);next(ht)){     
  14.     key = skey(ht);              /*获取当前字符串key*/    
  15.     value = *(double*)value(ht); /*获取当前double类型的value值,需要做类型转换*/    
  16.     printf("key: %s, value:%lf\n",key,value);    
  17. }  
 

接口设计:

 

为了向用户提供上述访问HashTable内容的方式,我们对HashTable的访问接口设计如下:

 

 

C代码   收藏代码
  1. #define create_hashtable(size, ...)           \  
  2.        _create_hashtable(size, #__VA_ARGS__)  
  3.   
  4. #define hash_add(ht,key,value)                \  
  5.        _hash_add((ht),(key),(value))  
  6.   
  7. #define hash_find(ht,key,value)               \  
  8.        _hash_find((ht),(key),(value))  
  9.   
  10. #define hash_del(ht,key)                      \  
  11.        _hash_del((ht),(key))  
  12.   
  13. #define hash_exists(ht,key)                   \  
  14.        _hash_exists((ht),(key))  
  15.   
  16. #define reset(ht)       ((ht)->pInternalPointer = (ht)->pListHead)  
  17. #define next(ht)        ((ht)->pInternalPointer = (ht)->pInternalPointer->pListNext)  
  18. #define isnotend(ht)    ((ht)->pInternalPointer != NULL)  
  19. #define nkey(ht)        ((ht)->pInternalPointer->h)  
  20. #define skey(ht)        ((ht)->pInternalPointer->key)  
  21. #define value(ht)       ((ht)->pInternalPointer->value)  
  22.   
  23.   
  24. HashTable * _create_hashtable(uint size, const char* s_typename);  
  25. int _hash_add(HashTable * ht, ...);  
  26. int _hash_find(HashTable * ht, ...);  
  27. int _hash_del(HashTable * ht, ...);  
  28. int _hash_exists(HashTable * ht, ...);  
  29. int  hash_num_elements(HashTable * ht);  
  30. void hash_free(HashTable * ht);  

 

 

上述结构设计和接口设计共同构成了我们HashTable的头文件hashtable.h

 

剩下的就是实现_create_hashtable、_hash_add、_hash_add、_hash_find、_hash_del、_hash_exists、hash_num_elements和hash_free函数了。

 

具体的实现细节和更多的测试用例,就不在此一一列出。

用户可以访问该项目在googlecode上的地址:https://code.google.com/p/c-hash/

里面有完整的项目代码,并提供动态库libht.so,及静态库libhts.a 的库文件.

 

最后,欢迎拍砖和提出各种改进意见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值