在软件开发中,不可不免的会使用到hash表,hash表的优点这里就不说了,以下介绍一个hash表的C实现,
uthash是用宏实现的,使用的时候非常方便,只用包含uthash.h即可。
Uthash的三个数据结构:
1. typedef struct UT_hash_bucket {
struct UT_hash_handle *hh_head;
unsigned count;
unsigned expand_mult;
} UT_hash_bucket;
UT_hash_bucket作用提供根据hash进行索引。
2.typedef struct UT_hash_table {
UT_hash_bucket *buckets;
unsigned num_buckets, log2_num_buckets;
unsigned num_items;
struct UT_hash_handle *tail; /* tail hh in app order, for fast append */
ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
unsigned ideal_chain_maxlen;
unsigned nonideal_items;
unsigned ineff_expands, noexpand;
uint32_t signature; /* used only to find hash tables in external analysis */
#ifdef HASH_BLOOM
uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
uint8_t *bloom_bv;
char bloom_nbits;
#endif
} UT_hash_table;
UT_hash_table可以看做hash表的表头。
3. typedef struct UT_hash_handle {
struct UT_hash_table *tbl;
void *prev; /* prev element in app order */
void *next; /* next element in app order */
struct UT_hash_handle *hh_prev; /* previous hh in bucket order */
struct UT_hash_handle *hh_next; /* next hh in bucket order */
void *key; /* ptr to enclosing struct's key */
unsigned keylen; /* enclosing struct's key len */
unsigned hashv; /* result of hash-fcn(key) */
} UT_hash_handle;
UT_hash_handle,用户自定义数据必须包含的结构。
三种数据结构的关系如下:
说明:
每一个节点(用户自定义的)必须包含一个UT_hash_handle hh
key:用户自定义,可以是int, string和指针。
hh_prev: 指向前一个UT_hash_handle
hh_next: 指向下一个UT_hash_handle
hashv:根据key计算出的hash值
prev: 指向前一个数据节点(Hash冲突时)
next: 指向下一个数据节点(Hash冲突时)
hho: 数据节点中hh于用户节点首地址的差。
--------------------------------------------我是分割线---------------------------------------
由于C语言中,并没有对hash表这类的高级数据结构进行支持,即使在目前通用的C++中,也只支持栈、队列等几个数据结构,对于map,其实是以树结构来实现的,而不是以hash表实现。
uthash是一个C语言的hash表实现。它以宏定义的方式实现hash表,不仅加快了运行的速度,而且与关键类型无关的优点。
uthash使用起来十分方便,只要将头文件uthash.h包含进去就可以使用。
目前,uthash的最新版本(1.9)支持如下平台:
通常这足够通用了。唯一的不足是在windows下,在uthash这旧版本中,并不支持vs2008和2010,而是支持MinGW。
uthash支持:增加、查找、删除、计数、迭代、排序、选择等操作。
第一步:包含头文件
第二步:自定义数据结构。每个结构代表一个键-值对,并且,每个结构中要有一个UT_hash_handle成员。
第三步:定义hash表指针。这个指针为前面自定义数据结构的指针,并初始化为NULL。
第四步:进行一般的操作。
增加:
查找:
删除:
计数:
迭代:
排序:
要注意,在uthash中,并不会对其所储存的值进行移动或者复制,也不会进行内存的释放。
--------------------------------------------我是分割线---------------------------------------
uthash使用代码例子
---------------------------------我是分割线-------------------------------------
4. 其他类型key的使用
本节主要关于key值类型为其他任意类型,例如整型、字符串、指针、结构体等时的用法。
注意:在使用key值为浮点类型时,由于浮点类型的比较受到精度的影响,例如:1.0000000002被认为与1相等,这些问题在uthash中也存在。
4.1. int类型key
前面就是以int类型的key作为示例,总结int类型key使用方法,可以看到其查找和插入分别使用专用接口:HASH_FIND_INT和ASH_ADD_INT。
4.2. 字符指针char*类型key与字符数组char key[100]类型key
特别注意在Strting类型中,uthash对指针char*和字符数组(例如char key[100])做了区分,这两种情况下使用的接口函数时不一样的。在添加的时候,key的类型为指针时使用接口函数HASH_ADD_KEYPTR,key的类型为字符数组时,使用接口函数HASH_ADD_STR,除了添加的接口不一样外,其他的查找、删除、变量等接口函数都是一样的。
4.3.使用地址作为key
在uthash中也可使用地址做key进行hash操作,使用地址作为key值时,其类型为void*,这样它就可以支持任意类型的地址了。在使用地址作为key时,插入和查找的专用接口函数为HASH_ADD_PTR和HASH_FIND_PTR,其余接口是一样的。
4.3.其他非常用类型key
在uthash中还可使用结构体作为key,甚至可以采用组合的方式让多个值作为key,这些在其官方的网站张均有较详细的使用示例。在使用uthash需要注意以下几点:
在定义hash结构体时不要忘记定义UT_hash_handle的变量
需确保key值唯一,如果插入key-value对时,key值已经存在,再插入的时候就会出错。
不同的key值,其增加和查找调用的接口函数不一样,具体可见第4节。一般情况下,不通类型的key,其插入和查找接口函数是不一样的,删除、遍历、元素统计接口是通用的,特殊情况下,字符数组和字符串作为key值时,其插入接口函数不一样,但是查找接口是一样的