參考:
[1] uthash | 學步園
[2] 源碼
[3] 官方文檔
一、哈希表的概念及作用
在一般的線性表或者樹中,我們所儲存的值寫它的存儲位置的關系是隨機的。因此,在查找過程中,需要一系列的與關鍵字的比較。算法的時間復雜度與比較的次數有關。線性表查找的時間復雜度為O(n)而平衡二叉樹的查找的時間復雜度為O(log(n))。無論是采用線程表或是樹進行存儲,都面臨面隨着數據量的增大,查找速度將不同程度變慢的問題。而哈希表正好解決了這個問題。它的主要思想是通過將值與其存儲位置相關聯,來實現快速的隨機存儲。
二、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,而是支持。
第一步:包含頭文件
#include <uthash.h>
怎么在codeblocks中使用uthash?
答:將uthash.h包復制到該文件項目下
代碼實例:
#include "uthash.h" #include <stdlib.h> /* malloc */ #include <stdio.h> /* printf */ typedef struct example_user_t { int id; int cookie; UT_hash_handle hh; } example_user_t; int main() { int i; example_user_t *user, *users=NULL; /* create elements */ for(i=0; i<10; i++) { user = (example_user_t*)malloc(sizeof(example_user_t)); if (user == NULL) { exit(-1); } user->id = i; user->cookie = i*i; HASH_ADD_INT(users,id,user); } for(user=users; user != NULL; user=(example_user_t*)(user->hh.next)) { printf("user %d, cookie %d\n", user->id, user->cookie); } return 0; }
第二步:自定義數據結構。每個結構代表一個鍵-值對,並且,每個結構中要有一個UT_hash_handle成員。
- id是鍵(key);
- name是值,即自己要保存的數據域,這里可以根據自己的需要讓它變成結構體指針或者其他類型都可以;
- hh是內部使用的hash處理句柄,在使用過程中,只需要在結構體中定義一個UT_hash_handle類型的變量即可,不需要為該句柄變量賦值,但必須在該結構體中定義該變量。
- Uthash所實現的hash表中可以提供類似於雙向鏈表的操作,可以通過結構體成員hh的
hh.prev
和hh.next獲取當前節點的上一個節點或者下一個節點。
typedef struct myhash{ int id; //key char name[10]; //value UT_hash_handle hh; //使此結構可哈希 };
第三步:定義hash表指針。這個指針為前面自定義數據結構的指針,並初始化為NULL。
struct myhash *users = NULL; //初始化為空
第四步:進行一般的操作。
增加:
- 向hashtable中添加數據
- key是int,可以使用 HASH_ADD_INT
- key是字符串,可以使用 HASH_ADD_STR
- key是指針,可以使用 HASH_ADD_PTR
- 其它,可以使用 HASH_ADD,上述實際都是調用這個方法,不過簡化了參數
void hashTabel_add(HashHead *head, HashNode *users) { // id是key的屬性名字,雖然很奇怪,實際作為宏參數會被替換掉 // 可以看下面源碼,intfield會替換換成&((add)->fieldname) if(!find_user(*head, users->id)) HASH_ADD_INT(*head, id, users); }
查找:
- 根據key查找節點
- 如果key是int,可以使用 HASH_FIND_INT..
HashNode *find_user(HashHead head, int user_id) { HashNode *s; HASH_FIND_INT(head, &user_id, s); /* s: output pointer */ return s; }
其實現過程就是先定義一個hash結構體指針變量,然后通過HASH_FIND_INT
接口找到該key所對應的hash結構體。這里需要注意:
1、Uthash為整型key提供的查找接口為HASH_FIND_INT
;
替換:
- 與添加差不多,會在添加前,刪除key相同的節點,再添加新的節點
- 如果key是int,可以使用 HASH_REPLACE_INT
void replace_user(HashHead *head, HashNode *newNode) { HashNode *oldNode = find_user(*head, newNode->id); if (oldNode) HASH_REPLACE_INT(*head, id, newNode, oldNode); }
刪除:
- 刪除節點
- 使用 HASH_DEL
void delete_user(HashHead *head,HashNode *user) { if (user) { HASH_DEL(*head, user); /* user: pointer to deletee */ free(user); /* optional; it's up to you! */ } }
只需要告訴該接口要釋放哪個hash表(這里是head
)里的哪個節點(這里是user),需要注意:釋放申請的hash結構體變量,uthash函數只將結構體從hash表中移除,並未釋放該結構體所占據的內存。
計數:
- 統計節點數
- 使用 HASH_COUNT
int count_user(HashHead head) { return HASH_COUNT(head); }
遍歷:
- 遍歷節點
- 可以用循環或者使用 HASH_ITER
void print_user(HashHead head) { HashNode *s; printf("size is %d\n", count_user(head)); for (s = head; s != NULL; s = s->hh.next) //類似於雙向鏈表 { printf("user id %d, name %s\n", s->id, s->name); } } void print_user_iterator(HashHead head) { HashNode *s, *tmp; printf("size is %d\n", count_user(head)); HASH_ITER(hh, head, s, tmp) { printf("user id %d: name %s\n", s->id, s->name); /* ... it is safe to delete and free s here */ } }
迭代:
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 } }
排序:
- 給節點排序,可以根據key或者value
- 使用 HASH_SORT
int name_sort(HashNode *a, HashNode *b) { return strcmp(a->name,b->name); } int id_sort(HashNode *a, HashNode *b) { return (a->id - b->id); } void sort_by_name(HashHead *head) { HASH_SORT(*head, name_sort); } void sort_by_id(HashHead *head) { HASH_SORT(*head, id_sort); }
要注意,在uthash中,並不會對其所儲存的值進行移動或者復制,也不會進行內存的釋放。
三、uthash的高級用法
關於鍵
對於uthash來說,鍵只是一系列的字節。所以,我們可以使用任意類型的鍵。包括:整形,字符串,結構等。uthash並不建議以浮點數作為鍵。若鍵的類型是結構體,那么在加入到hash表前,要對無關的數據進行清零。例如我們定義如下結構體:
typedef struct /* this structure will be our key */ { char a; int b; } record_key_t;
由於系統會自動進行字節對齊,也就是在a后面加入3個點位的字節。所以,在將類型為record_key_t 的數據加入到hash表前,一定要保證該數據的無關字節的值為0,否則將可能造成存入的鍵值無法被查找到的情況。
下面是一段使用結構體作為鍵的類型的代碼:
#include <stdlib.h> #include <stdio.h> #include "uthash.h" typedef struct /* this structure will be our key */ { char a; int b; } record_key_t; typedef struct /* the hash is comprised of these */ { record_key_t key; /* ... other data ... */ 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)); /* zero fill! */ r->key.a = 'a'; r->key.b = 1; HASH_ADD(hh, records, key, sizeof(record_key_t), r); memset(&l, 0, sizeof(record_t)); /* zero fill! */ 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把一切的鍵都看作是字節序列的原理,我們只要將組合鍵的第一個字段和整個組合鍵的長度正確的填入相關的函數就可以了。例子如下:
#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; /* these two fields */ int text[]; /* comprise the key */ } 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}; /* UTF-32LE for 鍖椾含 */ /* allocate and initialize our structure */ msg = (msg_t*)malloc( sizeof(msg_t) + sizeof(beijing) ); memset(msg, 0, sizeof(msg_t)+sizeof(beijing)); /* zero fill */ msg->len = sizeof(beijing); msg->encoding = UTF32; memcpy(msg->text, beijing, sizeof(beijing)); /* calculate the key length including padding, using formula */ keylen = offsetof(msg_t, text) /* offset of last key field */ + sizeof(beijing) /* size of last key field */ - offsetof(msg_t, encoding); /* offset of first key field */ /* add our structure to the hash table */ HASH_ADD( hh, msgs, encoding, keylen, msg); /* look it up to prove that it worked :-) */ 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表里,並且可以指定不同的字段做為鍵
例如:
#include <stdlib.h> /* malloc */ #include <stddef.h> /* offsetof */ #include <stdio.h> /* printf */ #include <string.h> /* memset */ #include "uthash.h" struct my_struct { int id; /* usual key */ char username[10]; /* alternative key */ UT_hash_handle hh1; /* handle for first hash table */ UT_hash_handle hh2; /* handle for second hash table */ }; int main() { 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"); /* add the structure to both hash tables */ HASH_ADD(hh1, users_by_id, id, sizeof(int), s); HASH_ADD(hh2, users_by_name, username, strlen(s->username), s); /* lookup user by ID in the "users_by_id" hash table */ i=1; HASH_FIND(hh1, users_by_id, &i, sizeof(int), s); if (s) printf("found id %d: %s/n", i, s->username); /* lookup user by username in the "users_by_name" hash table */ name = "thanson"; HASH_FIND(hh2, users_by_name, name, strlen(name), s); if (s) printf("found user %s: %d/n", name, s->id); return 0; }
注意,若要將結構體變量存儲在不同的hash表里,需要在該結構體中為每個hash表定義一個UT_hash_handle字段。
選擇
簡單來說,select就是在一個hash表選擇出一批數據,加入到另一個hash表中。它比用HASH_ADD更快。例子如下:
#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; /* create elements */ 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); } /* now select some users into ausers */ 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; }
全部代碼實例
#include <stdio.h> #include <stdlib.h> #include "uthash.h" typedef struct my_struct { int id; /* we'll use this field as the key */ char name[10]; UT_hash_handle hh; /* makes this structure hashable */ } HashNode; typedef HashNode* HashHead; int count_user(HashHead head); HashNode *find_user(HashHead head, int user_id) { HashNode *s; HASH_FIND_INT(head, &user_id, s); /* s: output pointer */ return s; } void add_user(HashHead *head, HashNode *users) { if(!find_user(*head, users->id)) HASH_ADD_INT(*head, id, users); } void replace_user(HashHead *head, HashNode *newNode) { HashNode *oldNode = find_user(*head, newNode->id); if (oldNode) HASH_REPLACE_INT(*head, id, newNode, oldNode); } void delete_user(HashHead *head,HashNode *user) { if (user) { HASH_DEL(*head, user); /* user: pointer to deletee */ free(user); /* optional; it's up to you! */ } } void print_user(HashHead head) { HashNode *s; printf("size is %d\n", count_user(head)); for (s = head; s != NULL; s = s->hh.next) { printf("user id %d, name %s\n", s->id, s->name); } } void print_user_iterator(HashHead head) { HashNode *s, *tmp; printf("size is %d\n", count_user(head)); HASH_ITER(hh, head, s, tmp) { printf("user id %d: name %s\n", s->id, s->name); /* ... it is safe to delete and free s here */ } } int count_user(HashHead head) { return HASH_COUNT(head); } int name_sort(HashNode *a, HashNode *b) { return strcmp(a->name,b->name); } int id_sort(HashNode *a, HashNode *b) { return (a->id - b->id); } void sort_by_name(HashHead *head) { HASH_SORT(*head, name_sort); } void sort_by_id(HashHead *head) { HASH_SORT(*head, id_sort); } int main() { printf("--------------init---------------\n"); HashHead head = NULL; printf("--------------add---------------\n"); HashNode *node = malloc(sizeof(HashNode)); node->id = 1; strcpy(node->name, "tom"); add_user(&head, node); node = malloc(sizeof(HashNode)); node->id = 2; strcpy(node->name, "jerry"); add_user(&head, node); node = malloc(sizeof(HashNode)); node->id = 3; strcpy(node->name, "jack"); add_user(&head, node); node = malloc(sizeof(HashNode)); node->id = 0; strcpy(node->name, "zero"); add_user(&head, node); print_user(head); printf("--------------replace---------------\n"); HashNode *newNode = malloc(sizeof(HashNode)); newNode->id = 3; strcpy(newNode->name, "rose"); replace_user(&head, newNode); print_user(head); printf("--------------delete---------------\n"); delete_user(&head, find_user(head, 1)); print_user(head); printf("--------------sort-by-id---------------\n"); sort_by_id(&head); print_user(head); printf("--------------sort-by-name---------------\n"); sort_by_name(&head); print_user(head); return 0; }