一、哈希表的概念及作用
在一般的线性表或者树中,我们所储存的值写它的存储位置的关系是随机的。因此,在查找过程中,需要一系列的与关键字的比较。算法的时间复杂度与比较的次数有关。线性表查找的时间复杂度为O(n)而平衡二叉树的查找的时间复杂度为O(log(n))。无论是采用线程表或是树进行存储,都面临面随着数据量的增大,查找速度将不同程度变慢的问题。而哈希表正好解决了这个问题。它的主要思想是通过将值与其存储位置相关联,来实现快速的随机存储。
关于哈希表的详细说明,可以参考以下链接:
http://zh.wikipedia.org/zh-cn/%E5%93%88%E5%B8%8C%E8%A1%A8
http://en.wikipedia.org/wiki/Hash_table
二、uthash的基本用法
由于C语言中,并没有对hash表这类的高级数据结构进行支持,即使在目前通用的C++中,也只支持栈、队列等几个数据结构,对于map,其实是以树结构来实现的,而不是以hash表实现。
uthash是一个C语言的hash表实现。它以宏定义的方式实现hash表,不仅加快了运行的速度,而且与关键类型无关的优点。
uthash使用起来十分方便,只要将头文件uthash.h包含进去就可以使用。
目前,uthash的最新版本(1.9)支持如下平台:
- Linux
- Mac OS X
- Windows using vs2008 and vs 2010
- Solaris
- OpenBSD
通常这足够通用了。唯一的不足是在windows下,在uthash这旧版本中,并不支持vs2008和2010,而是支持MinGW。
uthash支持:增加、查找、删除、计数、迭代、排序、选择等操作。
第一步:包含头文件
[c-sharp] view plain copy
- #include "uthash.h"
第二步:自定义数据结构。每个结构代表一个键-值对,并且,每个结构中要有一个UT_hash_handle成员。
[c-sharp] view plain copy
- struct my_struct {
- int id;
- char name[10];
- UT_hash_handle hh;
- };
第三步:定义hash表指针。这个指针为前面自定义数据结构的指针,并初始化为NULL。
[c-sharp] view plain copy
- struct my_struct *users = NULL;
第四步:进行一般的操作。
增加:
[c-sharp] view plain copy
- int add_user(int user_id, char *name) {
- struct my_struct *s;
- s = malloc(sizeof(struct my_struct));
- s->id = user_id;
- strcpy(s->name, name);
- HASH_ADD_INT( users, id, s );
- }
查找:
[c-sharp] view plain copy
- struct my_struct *find_user(int user_id) {
- struct my_struct *s;
- HASH_FIND_INT( users, &user_id, s );
- return s;
- }
删除:
[c-sharp] view plain copy
- void delete_user(struct my_struct *user) {
- HASH_DEL( users, user);
- free(user);
- }
计数:
[c-sharp] view plain copy
- unsigned int num_users;
- num_users = HASH_COUNT(users);
- printf("there are %u users/n", num_users);
迭代:
[c-sharp] view plain copy
- void print_users() {
- struct my_struct *s;
- for(s=users; s != NULL; s=s->hh.next) {
- printf("user id %d: name %s/n", s->id, s->name
- }
- }
排序:
[c-sharp] view plain copy
- int name_sort(struct my_struct *a, struct my_struct *b) {
- return strcmp(a->name,b->name);
- }
- int id_sort(struct my_struct *a, struct my_struct *b) {
- return (a->id - b->id);
- }
- void sort_by_name() {
- HASH_SORT(users, name_sort);
- }
- void sort_by_id() {
- HASH_SORT(users, id_sort);
- }
要注意,在uthash中,并不会对其所储存的值进行移动或者复制,也不会进行内存的释放。
三、uthash的高级用法
第一、关于键
对于uthash来说,键只是一系列的字节。所以,我们可以使用任意类型的键。包括:整形,字符串,结构等。uthash并不建议以浮点数作为键。若键的类型是结构体,那么在加入到hash表前,要对无关的数据进行清零。例如我们定义如下结构体:
[c-sharp] view plain copy
- typedef struct {
- char a;
- int b;
- } record_key_t;
由于系统会自动进行字节对齐,也就是在a后面加入3个点位的字节。所以,在将类型为record_key_t*的数据加入到hash表前,一定要保证该数据的无关字节的值为0,否则将可能造成存入的键值无法被查找到的情况。
下面是一段使用结构体作为键的类型的代码:
[c-sharp] view plain copy
- #include <stdlib.h>
- #include <stdio.h>
- #include "uthash.h"
- typedef struct {
- char a;
- int b;
- } record_key_t;
- typedef struct {
- record_key_t key;
-
- UT_hash_handle hh;
- } record_t;
- int main(int argc, char *argv[]) {
- record_t l, *p, *r, *records = NULL;
- r = (record_t*)malloc( sizeof(record_t) );
- memset(r, 0, sizeof(record_t));
- r->key.a = ’a’;
- r->key.b = 1;
- HASH_ADD(hh, records, key, sizeof(record_key_t), r);
- memset(&l, 0, sizeof(record_t));
- l.key.a = ’a’;
- l.key.b = 1;
- HASH_FIND(hh, records, &l.key, sizeof(record_key_t), p);
- if (p) printf("found %c %d/n", p->key.a, p->key.b);
- return 0;
- }
uthash中还支持组合键,但是要求组合键的存储位置是相邻的。所谓的组合键也是利用了uthash把一切的键都看作是字节序列的原理,我们只要将组合键的第一个字段和整个组合键的长度正确的填入相关的函数就可以了。例子如下:
[c-sharp] view plain copy
- #include <stdlib.h> /* malloc */
- #include <stddef.h> /* offsetof */
- #include <stdio.h> /* printf */
- #include <string.h> /* memset */
- #include "uthash.h"
- #define UTF32 1
- typedef struct {
- UT_hash_handle hh;
- int len;
- char encoding;
- int text[];
- } msg_t;
- typedef struct {
- char encoding;
- int text[];
- } lookup_key_t;
- int main(int argc, char *argv[]) {
- unsigned keylen;
- msg_t *msg, *msgs = NULL;
- lookup_key_t *lookup_key;
- int beijing[] = {0x5317, 0x4eac};
-
- msg = (msg_t*)malloc( sizeof(msg_t) + sizeof(beijing) );
- memset(msg, 0, sizeof(msg_t)+sizeof(beijing));
- msg->len = sizeof(beijing);
- msg->encoding = UTF32;
- memcpy(msg->text, beijing, sizeof(beijing));
-
- keylen = offsetof(msg_t, text)
- + sizeof(beijing)
- - offsetof(msg_t, encoding);
-
- HASH_ADD( hh, msgs, encoding, keylen, msg);
-
- msg=NULL;
- lookup_key = (lookup_key_t*)malloc(sizeof(*lookup_key) + sizeof(beijing));
- memset(lookup_key, 0, sizeof(*lookup_key) + sizeof(beijing));
- lookup_key->encoding = UTF32;
- memcpy(lookup_key->text, beijing, sizeof(beijing));
- HASH_FIND( hh, msgs, &lookup_key->encoding, keylen, msg );
- if (msg) printf("found /n");
- free(lookup_key);
- return 0;
- }
第二、uthash支持将一个结构体变量存储在不同的hash表里,并且可以指定不同的字段做为键。例如:
[c-sharp] view plain copy
- struct my_struct {
- int id;
- char username[10];
- UT_hash_handle hh1;
- UT_hash_handle hh2;
- };
- struct my_struct *users_by_id = NULL, *users_by_name = NULL, *s;
- int i;
- char *name;
- s = malloc(sizeof(struct my_struct));
- s->id = 1;
- strcpy(s->username, "thanson");
-
- HASH_ADD(hh1, users_by_id, id, sizeof(int), s);
- HASH_ADD(hh2, users_by_name, username, strlen(s->username), s);
-
- i=1;
- HASH_FIND(hh1, users_by_id, &i, sizeof(int), s);
- if (s) printf("found id %d: %s/n", i, s->username);
-
- name = "thanson";
- HASH_FIND(hh2, users_by_name, name, strlen(name), s);
- if (s) printf("found user %s: %d/n", name, s->id);
注意,若要将结构体变量存储在不同的hash表里,需要在该结构体中为每个hash表定义一个UT_hash_handle字段。
第三、选择。简单来说,select就是在一个hash表选择出一批数据,加入到另一个hash表中。它比用HASH_ADD更快。例子如下:
[c-sharp] view plain copy
- #include "uthash.h"
- #include <stdlib.h> /* malloc */
- #include <stdio.h> /* printf */
- typedef struct {
- int id;
- UT_hash_handle hh;
- UT_hash_handle ah;
- } example_user_t;
- #define EVENS(x) (((x)->id & 1) == 0)
- int evens(void *userv) {
- example_user_t *user = (example_user_t*)userv;
- return ((user->id & 1) ? 0 : 1);
- }
- int idcmp(void *_a, void *_b) {
- example_user_t *a = (example_user_t*)_a;
- example_user_t *b = (example_user_t*)_b;
- return (a->id - b->id);
- }
- int main(int argc,char *argv[]) {
- int i;
- example_user_t *user, *users=NULL, *ausers=NULL;
-
- for(i=0;i<10;i++) {
- user = (example_user_t*)malloc(sizeof(example_user_t));
- user->id = i;
- HASH_ADD_INT(users,id,user);
- }
- for(user=users; user; user=(example_user_t*)(user->hh.next)) {
- printf("user %d/n", user->id);
- }
-
- HASH_SELECT(ah,ausers,hh,users,evens);
- HASH_SRT(ah,ausers,idcmp);
- for(user=ausers; user; user=(example_user_t*)(user->ah.next)) {
- printf("auser %d/n", user->id);
- }
- return 0;
- }
第四、内置hash函数。uthash支持如下几种hash函数:
[c-sharp] view plain copy
- Symbol Name
- JEN Jenkins (default)
- BER Bernstein
- SAX Shift-Add-Xor
- OAT One-at-a-time
- FNV Fowler/Noll/Vo
- SFH Paul Hsieh
- MUR MurmurHash (see note)
编译代码时,可以选择不同的hash函数,只要在编译时指定就可以了。例如:
[c-sharp] view plain copy
- cc -DHASH_FUNCTION=HASH_BER -o program program.c
第五、线程。uthash是非线程安全的,所以如要在多线程中进行使用,必须对操作进行加锁。例如:
首先初始化锁
[c-sharp] view plain copy
- pthread_rwlock_t lock;
- if (pthread_rwlock_init(&lock,NULL) != 0) fatal("can’t create rwlock");
如果进行查找操作
[c-sharp] view plain copy
- if (pthread_rwlock_rdlock(&lock) != 0) fatal("can’t get rdlock");
- HASH_FIND_INT(elts, &i, e);
- pthread_rwlock_unlock(&lock);
如果进行删除操作
[c-sharp] view plain copy
- if (pthread_rwlock_wrlock(&lock) != 0) fatal("can’t get wrlock");
- HASH_DEL(elts, e);
- pthread_rwlock_unlock(&lock);
----------------------------------------------------------------------------------------------------------------
demo:
- #include "uthash.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
-
-
- struct packet
- {
- int key;
- char msg[10];
- UT_hash_handle hh;
- };
-
- int main()
- {
- struct packet *pkt, *tmp;
- int i;
-
- struct packet *hash_packet = NULL;
-
-
-
-
- printf ("hash count = %d \n", HASH_COUNT(hash_packet));
-
-
-
-
-
-
- for (i=0; i<10; i++)
- {
- pkt = (struct packet *)malloc(sizeof(struct packet));
- pkt->key=i;
- sprintf (pkt->msg, "i=%d", i);
-
- HASH_FIND_INT(hash_packet, &i, tmp);
- if (tmp != NULL)
- {
- printf ("The key(%d) exists in hash. \n", i);
- continue;
- }
- HASH_ADD_INT(hash_packet, key, pkt);
- printf ("insert item. key=%d,value=%p \n", i, pkt);
- }
- printf ("hash count = %d \n", HASH_COUNT(hash_packet));
-
-
-
-
-
- for (i=0; i<13; i++)
- {
- HASH_FIND_INT(hash_packet, &i, tmp);
- if (tmp == NULL)
- {
- printf ("find not item. key=%d,value=%p \n", i, tmp);
- continue;
- }
- printf ("find item. key=%d,value=%p \n", i, tmp);
- }
-
- printf ("hash count = %d \n", HASH_COUNT(hash_packet));
-
-
-
-
-
- for (tmp=hash_packet; tmp != NULL; tmp=tmp->hh.next)
- printf (" %d => %s \n", tmp->key, tmp->msg);
-
- for (i=0; i<13; i++)
- {
- HASH_FIND_INT(hash_packet, &i, tmp);
- if (tmp == NULL)
- {
- printf ("find not item. key=%d,value=%p \n", i, tmp);
- continue;
- }
-
-
- HASH_DEL(hash_packet, tmp);
- free(tmp);
- printf ("delete itme. key=%d,value=%p \n", i, tmp);
- }
- printf ("hash count = %d \n", HASH_COUNT(hash_packet));
- return 0;
- }