uthash 是C的比較優秀的開源代碼,它實現了常見的hash操作函數,例如查找、插入、刪除等待。該套開源代碼采用宏的方式實現hash函數的相關功能,支持C語言的任意數據結構最為key值,甚至可以采用多個值作為key,無論是自定義的struct還是基本數據類型,需要注意的是不同類型的key其操作接口方式略有不通。
使用uthash代碼時只需要包含頭文件"uthash.h"即可。由於該代碼采用宏的方式實現,所有的實現代碼都在uthash.h文件中,因此只需要在自己的代碼中包含該頭文件即可。可以通過下面兩種方式獲取源代碼:
- 通過官方下載鏈接:
https://github.com/troydhanson/uthash
- 另外,uthash的英文使用文檔介紹可從下面網址獲得:
http://troydhanson.github.io/uthash/userguide.html#_add_item
1.uthash的效率
uthash的插入、查找、刪除的操作時間都是常量,當然這個常量的值收到key以及所選擇的hash函數的影響,uthash共提供了7中函數函數,一般情況下選擇默認的即可。如果對效率要求特別高時,可以再根據自己的需求選擇適合自己的hash函數。
2、uthash的使用
在hash操作中,都是按照“鍵-值“對的方式進行插、查等操作,在uthash中,其基本數據結構就是一個包含“鍵-值“對的結構體,另外,該結構體中還包含一個uthash內部使用的hash處理句柄,如下代碼所示:
1 #include"uthash.h" 2 3 struct my_struct { 4 int id; /* key */ 5 char name[10]; 6 UT_hash_handle hh; /* makes this structure hashable */ 7 };
其中:
- id是鍵(key);
- name是值,即自己要保存的數據域,這里可以根據自己的需要讓它變成結構體指針或者其他類型都可以;
- hh是內部使用的hash處理句柄,在使用過程中,只需要在結構體中定義一個UT_hash_handle類型的變量即可,不需要為該句柄變量賦值,但必須在該結構體中定義該變量。
- Uthash所實現的hash表中可以提供類似於雙向鏈表的操作,可以通過結構體成員hh的
hh.prev
和hh.next獲取當前節點的上一個節點或者下一個節點。
3.Key類型為int的簡單示例
1)定義一個鍵為int類型的hash結構體:
1 #include "uthash.h" 2 3 struct my_struct { 4 int ikey; /* key */ 5 char value[10]; 6 UT_hash_handle hh; 7 }; 8 struct my_struct *g_users = NULL;
這里需要注意:
- key的類型為int,key的類型不一樣,后面的插入、查找調用的接口函數就不一樣,因此要求確保key的類型與uthash的接口函數一致。
- 必須提供
UT_hash_handle
變量hh
,無需為其初始化。 - 定義一個hash結構的空指針
users
,用於指向保存數據的hash表,必須初始化為空,在后面的查、插等操作中,uthash內部會根據其是否為空而進行不同的操作。
2)實現自己的查找接口函數:
1 struct my_struct *find_user(int ikey) { 2 struct my_struct *s; 3 HASH_FIND_INT(g_users, &ikey, s ); 4 return s; 5 }
其實現過程就是先定義一個hash結構體指針變量,然后通過
HASH_FIND_INT
接口找到該key所對應的hash結構體。這里需要注意:
- Uthash為整型key提供的查找接口為
HASH_FIND_INT
; - 傳給接口
HASH_FIND_INT
的第一個參數就是在1)中定義的指向hash表的指針,傳入的第二個參數是整型變量ikey的地址。
3)實現自己的插入接口函數:
void add_user(int ikey, char *value_buf) { struct my_struct *s; HASH_FIND_INT(g_users, &ikey, s); /* 插入前先查看key值是否已經在hash表g_users里面了 */ if (s==NULL) { s = (struct my_struct*)malloc(sizeof(struct my_struct)); s->ikey = ikey; HASH_ADD_INT(g_users, ikey, s ); /* 這里必須明確告訴插入函數,自己定義的hash結構體中鍵變量的名字 */ } strcpy(s-> value, value_buf); }
由於uthash要求鍵(key)必須唯一,而uthash內部未對key值得唯一性進行很好的處理,因此它要求外部在插入操作時要確保其key值不在當前的hash表中,這就需要,在插入操作時,先查找hash表看其值是否已經存在,不存在在時再進行插入操作,在這里需要特別注意以下兩點:
- 插入時,先查找,當鍵不在當前的hash表中時再進行插入,以確保鍵的唯一性。
- 需調用插入接口函數時需要明確告訴接口函數,自己定義的鍵變量的名字是什么。
4)實現刪除接口
void delete_user(int ikey) { struct my_struct *s = NULL; HASH_FIND_INT(g_users, &ikey, s); if (s!=NULL) { HASH_DEL(g_users, s); free(s); } }
刪除操作的接口函數為HASH_DEL
,只需要告訴該接口要釋放哪個hash表(這里是g_users
)里的哪個節點(這里是s),需要注意:釋放申請的hash結構體變量,uthash函數只將結構體從hash表中移除,並未釋放該結構體所占據的內存。
5)清空hash表
void delete_all() { struct my_struct *current_user, *tmp; HASH_ITER(hh, users, current_user, tmp) { HASH_DEL(g_users,current_user); free(current_user); } }
這里需要注意:uthash內部提供了另外一個清空函數:
HASH_CLEAR(hh, g_users);
函數,但它不釋放各節點的內存,因此盡量不要使用它,
6)統計hash表中的已經存在的元素數
該操作使用函數
HASH_COUNT
即可獲取到當前hash表中的元素數,其用法為:
unsigned int num_users; num_users = HASH_COUNT(g_users); printf("there are %u items\n", num_users);
7、遍歷元素
在開發過程中,可能需要對整個hash表進行遍歷,這里可以通過
hh.next
獲取當前元素的下一個元素。具體遍歷方法為:
struct my_struct *s, *tmp; HASH_ITER(hh, g_users, s, tmp) { printf("user ikey %d: value %s\n", s->ikey, s->value); /* ... it is safe to delete and free s here */ }
另外還有一種不安全的刪除方法,盡量避免使用它:
void print_users() { struct my_struct *s; for(s=g_users; s != NULL; s=s->hh.next) { printf("user ikey %d: value %s\n", s->ikey, s->value); } }
4. 其他類型key的使用
本節主要關於key值類型為其他任意類型,例如整型、字符串、指針、結構體等時的用法。
注意:在使用key值為浮點類型時,由於浮點類型的比較受到精度的影響,例如:1.0000000002被認為與1相等,這些問題在uthash中也存在。
4.1. int類型key
前面就是以int類型的key作為示例,總結int類型key使用方法,可以看到其查找和插入分別使用專用接口:HASH_FIND_INT和HASH_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值時,其插入接口函數不一樣,但是查找接口是一樣的。
5.完整程序例子
5.1.key類型為int的完整的例子
1 #include <stdio.h> /* gets */ 2 #include <stdlib.h> /* atoi, malloc */ 3 #include <string.h> /* strcpy */ 4 #include "uthash.h" 5 6 struct my_struct { 7 int ikey; /* key */ 8 char value[10]; 9 UT_hash_handle hh; /* makes this structure hashable */ 10 }; 11 12 static struct my_struct *g_users = NULL; 13 14 void add_user(int mykey, char *value) { 15 struct my_struct *s; 16 17 HASH_FIND_INT(users, &mykey, s); /* mykey already in the hash? */ 18 if (s==NULL) { 19 s = (struct my_struct*)malloc(sizeof(struct my_struct)); 20 s->ikey = mykey; 21 HASH_ADD_INT( users, ikey, s ); /* ikey: name of key field */ 22 } 23 strcpy(s->value, value); 24 } 25 26 struct my_struct *find_user(int mykey) { 27 struct my_struct *s; 28 29 HASH_FIND_INT( users, &mykey, s ); /* s: output pointer */ 30 return s; 31 } 32 33 void delete_user(struct my_struct *user) { 34 HASH_DEL( users, user); /* user: pointer to deletee */ 35 free(user); 36 } 37 38 void delete_all() { 39 struct my_struct *current_user, *tmp; 40 41 HASH_ITER(hh, users, current_user, tmp) { 42 HASH_DEL(users,current_user); /* delete it (users advances to next) */ 43 free(current_user); /* free it */ 44 } 45 } 46 47 void print_users() { 48 struct my_struct *s; 49 50 for(s=users; s != NULL; s=(struct my_struct*)(s->hh.next)) { 51 printf("user ikey %d: value %s\n", s->ikey, s->value); 52 } 53 } 54 55 int name_sort(struct my_struct *a, struct my_struct *b) { 56 return strcmp(a->value,b->value); 57 } 58 59 int id_sort(struct my_struct *a, struct my_struct *b) { 60 return (a->ikey - b->ikey); 61 } 62 63 void sort_by_name() { 64 HASH_SORT(users, name_sort); 65 } 66 67 void sort_by_id() { 68 HASH_SORT(users, id_sort); 69 } 70 71 int main(int argc, char *argv[]) { 72 char in[10]; 73 int ikey=1, running=1; 74 struct my_struct *s; 75 unsigned num_users; 76 77 while (running) { 78 printf(" 1. add user\n"); 79 printf(" 2. add/rename user by id\n"); 80 printf(" 3. find user\n"); 81 printf(" 4. delete user\n"); 82 printf(" 5. delete all users\n"); 83 printf(" 6. sort items by name\n"); 84 printf(" 7. sort items by id\n"); 85 printf(" 8. print users\n"); 86 printf(" 9. count users\n"); 87 printf("10. quit\n"); 88 gets(in); 89 switch(atoi(in)) { 90 case 1: 91 printf("name?\n"); 92 add_user(ikey++, gets(in)); 93 break; 94 case 2: 95 printf("id?\n"); 96 gets(in); ikey = atoi(in); 97 printf("name?\n"); 98 add_user(ikey, gets(in)); 99 break; 100 case 3: 101 printf("id?\n"); 102 s = find_user(atoi(gets(in))); 103 printf("user: %s\n", s ? s->value : "unknown"); 104 break; 105 case 4: 106 printf("id?\n"); 107 s = find_user(atoi(gets(in))); 108 if (s) delete_user(s); 109 else printf("id unknown\n"); 110 break; 111 case 5: 112 delete_all(); 113 break; 114 case 6: 115 sort_by_name(); 116 break; 117 case 7: 118 sort_by_id(); 119 break; 120 case 8: 121 print_users(); 122 break; 123 case 9: 124 num_users=HASH_COUNT(users); 125 printf("there are %u users\n", num_users); 126 break; 127 case 10: 128 running=0; 129 break; 130 } 131 } 132 133 delete_all(); /* free any structures */ 134 return 0; 135 }
5.2.key類型為字符數組的完整的例子
1 #include <string.h> /* strcpy */ 2 #include <stdlib.h> /* malloc */ 3 #include <stdio.h> /* printf */ 4 #include "uthash.h" 5 6 struct my_struct { 7 char name[10]; /* key (string is WITHIN the structure) */ 8 int id; 9 UT_hash_handle hh; /* makes this structure hashable */ 10 }; 11 12 13 int main(int argc, char *argv[]) { 14 const char **n, *names[] = { "joe", "bob", "betty", NULL }; 15 struct my_struct *s, *tmp, *users = NULL; 16 int i=0; 17 18 for (n = names; *n != NULL; n++) { 19 s = (struct my_struct*)malloc(sizeof(struct my_struct)); 20 strncpy(s->name, *n,10); 21 s->id = i++; 22 HASH_ADD_STR( users, name, s ); 23 } 24 25 HASH_FIND_STR( users, "betty", s); 26 if (s) printf("betty's id is %d\n", s->id); 27 28 /* free the hash table contents */ 29 HASH_ITER(hh, users, s, tmp) { 30 HASH_DEL(users, s); 31 free(s); 32 } 33 return 0; 34 }
5.3.key類型為字符指針的完整的例子
1 #include <string.h> /* strcpy */ 2 #include <stdlib.h> /* malloc */ 3 #include <stdio.h> /* printf */ 4 #include "uthash.h" 5 6 struct my_struct { 7 const char *name; /* key */ 8 int id; 9 UT_hash_handle hh; /* makes this structure hashable */ 10 }; 11 12 13 int main(int argc, char *argv[]) { 14 const char **n, *names[] = { "joe", "bob", "betty", NULL }; 15 struct my_struct *s, *tmp, *users = NULL; 16 int i=0; 17 18 for (n = names; *n != NULL; n++) { 19 s = (struct my_struct*)malloc(sizeof(struct my_struct)); 20 s->name = *n; 21 s->id = i++; 22 HASH_ADD_KEYPTR( hh, users, s->name, strlen(s->name), s ); 23 } 24 25 HASH_FIND_STR( users, "betty", s); 26 if (s) printf("betty's id is %d\n", s->id); 27 28 /* free the hash table contents */ 29 HASH_ITER(hh, users, s, tmp) { 30 HASH_DEL(users, s); 31 free(s); 32 } 33 return 0; 34 }
轉載自:
https://blog.csdn.net/whatday/article/details/95926766
其余介紹ut_hash的的博客如下:
https://blog.csdn.net/shenwansangz/article/details/48729969
https://www.cnblogs.com/aclove/p/3803110.html