参考:

[1] uthash | 学步园

[2] 源码

[3] 官方文档

[4]

[5]

[6]

一、哈希表的概念及作用

在一般的线性表或者树中,我们所储存的值写它的存储位置的关系是随机的。因此,在查找过程中,需要一系列的与关键字的比较。算法的时间复杂度与比较的次数有关。线性表查找的时间复杂度为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,而是支持。

第一步:包含头文件

  1. #include <uthash.h>

怎么在codeblocks中使用uthash?

答:将uthash.h包复制到该文件项目下

代码实例:

  1. #include "uthash.h"
  2. #include <stdlib.h> /* malloc */
  3. #include <stdio.h> /* printf */
  4.  
  5. typedef struct example_user_t {
  6. int id;
  7. int cookie;
  8. UT_hash_handle hh;
  9. } example_user_t;
  10.  
  11. int main()
  12. {
  13. int i;
  14. example_user_t *user, *users=NULL;
  15.  
  16. /* create elements */
  17. for(i=; i<; i++) {
  18. user = (example_user_t*)malloc(sizeof(example_user_t));
  19. if (user == NULL) {
  20. exit(-);
  21. }
  22. user->id = i;
  23. user->cookie = i*i;
  24. HASH_ADD_INT(users,id,user);
  25. }
  26.  
  27. for(user=users; user != NULL; user=(example_user_t*)(user->hh.next)) {
  28. printf("user %d, cookie %d\n", user->id, user->cookie);
  29. }
  30. return ;
  31. }

第二步:自定义数据结构。每个结构代表一个键-值对,并且,每个结构中要有一个UT_hash_handle成员。

  • id是键(key);
  • name是值,即自己要保存的数据域,这里可以根据自己的需要让它变成结构体指针或者其他类型都可以;
  • hh是内部使用的hash处理句柄,在使用过程中,只需要在结构体中定义一个UT_hash_handle类型的变量即可,不需要为该句柄变量赋值,但必须在该结构体中定义该变量。
  • Uthash所实现的hash表中可以提供类似于双向链表的操作,可以通过结构体成员hh的 hh.prevhh.next获取当前节点的上一个节点或者下一个节点。
  1. typedef struct myhash{
  2. int id; //key
  3. char name[]; //value
  4. UT_hash_handle hh; //使此结构可哈希
  5. };

第三步:定义hash表指针。这个指针为前面自定义数据结构的指针,并初始化为NULL。

  1. struct myhash *users = NULL; //初始化为空

第四步:进行一般的操作

增加:

  • 向hashtable中添加数据
  • key是int,可以使用 HASH_ADD_INT
  • key是字符串,可以使用 HASH_ADD_STR
  • key是指针,可以使用 HASH_ADD_PTR
  • 其它,可以使用 HASH_ADD,上述实际都是调用这个方法,不过简化了参数
  1. void hashTabel_add(HashHead *head, HashNode *users)
  2. {
  3. // id是key的属性名字,虽然很奇怪,实际作为宏参数会被替换掉
  4. // 可以看下面源码,intfield会替换换成&((add)->fieldname)
  5. if(!find_user(*head, users->id))
  6. HASH_ADD_INT(*head, id, users);
  7. }

查找:

  • 根据key查找节点
  • 如果key是int,可以使用 HASH_FIND_INT..
  1. HashNode *find_user(HashHead head, int user_id)
  2. {
  3. HashNode *s;
  4. HASH_FIND_INT(head, &user_id, s); /* s: output pointer */
  5. return s;
  6. }

其实现过程就是先定义一个hash结构体指针变量,然后通过HASH_FIND_INT接口找到该key所对应的hash结构体。这里需要注意:

1、Uthash为整型key提供的查找接口为HASH_FIND_INT

替换:

  • 与添加差不多,会在添加前,删除key相同的节点,再添加新的节点
  • 如果key是int,可以使用 HASH_REPLACE_INT
  1. void replace_user(HashHead *head, HashNode *newNode)
  2. {
  3. HashNode *oldNode = find_user(*head, newNode->id);
  4. if (oldNode)
  5. HASH_REPLACE_INT(*head, id, newNode, oldNode);
  6. }

删除:

  • 删除节点
  • 使用 HASH_DEL
  1. void delete_user(HashHead *head,HashNode *user)
  2. {
  3. if (user)
  4. {
  5. HASH_DEL(*head, user); /* user: pointer to deletee */
  6. free(user); /* optional; it's up to you! */
  7. }
  8. }

只需要告诉该接口要释放哪个hash表(这里是head)里的哪个节点(这里是user),需要注意:释放申请的hash结构体变量,uthash函数只将结构体从hash表中移除,并未释放该结构体所占据的内存。

计数:

  • 统计节点数
  • 使用 HASH_COUNT
  1. int count_user(HashHead head)
  2. {
  3. return HASH_COUNT(head);
  4. }

遍历:

  • 遍历节点
  • 可以用循环或者使用 HASH_ITER
  1. void print_user(HashHead head)
  2. {
  3. HashNode *s;
  4. printf("size is %d\n", count_user(head));
  5. for (s = head; s != NULL; s = s->hh.next) //类似于双向链表
  6. {
  7. printf("user id %d, name %s\n", s->id, s->name);
  8. }
  9. }
  10. void print_user_iterator(HashHead head)
  11. {
  12. HashNode *s, *tmp;
  13. printf("size is %d\n", count_user(head));
  14. HASH_ITER(hh, head, s, tmp)
  15. {
  16. printf("user id %d: name %s\n", s->id, s->name);
  17. /* ... it is safe to delete and free s here */
  18. }
  19. }

迭代:

  1. void print_users()
  2. {
  3. struct my_struct *s;
  4. for(s=users; s != NULL; s=s->hh.next)
  5. {
  6. printf("user id %d: name %s/n", s->id, s->name
  7. }
  8. }

排序:

  • 给节点排序,可以根据key或者value
  • 使用 HASH_SORT
  1. int name_sort(HashNode *a, HashNode *b)
  2. {
  3. return strcmp(a->name,b->name);
  4. }
  5.  
  6. int id_sort(HashNode *a, HashNode *b)
  7. {
  8. return (a->id - b->id);
  9. }
  10.  
  11. void sort_by_name(HashHead *head)
  12. {
  13. HASH_SORT(*head, name_sort);
  14. }
  15.  
  16. void sort_by_id(HashHead *head)
  17. {
  18. HASH_SORT(*head, id_sort);
  19. }

要注意,在uthash中,并不会对其所储存的值进行移动或者复制,也不会进行内存的释放。

三、uthash的高级用法

关于键

对于uthash来说,键只是一系列的字节。所以,我们可以使用任意类型的键。包括:整形,字符串,结构等。uthash并不建议以浮点数作为键。若键的类型是结构体,那么在加入到hash表前,要对无关的数据进行清零。例如我们定义如下结构体:

  1. typedef struct /* this structure will be our key */
  2. {
  3. char a;
  4. int b;
  5. } record_key_t;

由于系统会自动进行字节对齐,也就是在a后面加入3个点位的字节。所以,在将类型为record_key_t 的数据加入到hash表前,一定要保证该数据的无关字节的值为0,否则将可能造成存入的键值无法被查找到的情况。

下面是一段使用结构体作为键的类型的代码:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include "uthash.h"
  4. typedef struct /* this structure will be our key */
  5. {
  6. char a;
  7. int b;
  8. } record_key_t;
  9. typedef struct /* the hash is comprised of these */
  10. {
  11. record_key_t key;
  12. /* ... other data ... */
  13. UT_hash_handle hh;
  14. } record_t;
  15. int main(int argc, char *argv[])
  16. {
  17. record_t l, *p, *r, *records = NULL;
  18. r = (record_t*)malloc( sizeof(record_t) );
  19. memset(r, , sizeof(record_t)); /* zero fill! */
  20. r->key.a = 'a';
  21. r->key.b = ;
  22. HASH_ADD(hh, records, key, sizeof(record_key_t), r);
  23. memset(&l, , sizeof(record_t)); /* zero fill! */
  24. l.key.a = 'a';
  25. l.key.b = ;
  26. HASH_FIND(hh, records, &l.key, sizeof(record_key_t), p);
  27. if (p) printf("found %c %d/n", p->key.a, p->key.b);
  28. return ;
  29. }

uthash中还支持组合键,但是要求组合键的存储位置是相邻的。所谓的组合键也是利用了uthash把一切的键都看作是字节序列的原理,我们只要将组合键的第一个字段和整个组合键的长度正确的填入相关的函数就可以了。例子如下:

  1. #include <stdlib.h> /* malloc */
  2. #include <stddef.h> /* offsetof */
  3. #include <stdio.h> /* printf */
  4. #include <string.h> /* memset */
  5. #include "uthash.h"
  6. #define UTF32 1
  7. typedef struct
  8. {
  9. UT_hash_handle hh;
  10. int len;
  11. char encoding; /* these two fields */
  12. int text[]; /* comprise the key */
  13. } msg_t;
  14. typedef struct
  15. {
  16. char encoding;
  17. int text[];
  18. } lookup_key_t;
  19. int main(int argc, char *argv[])
  20. {
  21. unsigned keylen;
  22. msg_t *msg, *msgs = NULL;
  23. lookup_key_t *lookup_key;
  24. int beijing[] = {0x5317, 0x4eac}; /* UTF-32LE for 鍖椾含 */
  25. /* allocate and initialize our structure */
  26. msg = (msg_t*)malloc( sizeof(msg_t) + sizeof(beijing) );
  27. memset(msg, , sizeof(msg_t)+sizeof(beijing)); /* zero fill */
  28. msg->len = sizeof(beijing);
  29. msg->encoding = UTF32;
  30. memcpy(msg->text, beijing, sizeof(beijing));
  31. /* calculate the key length including padding, using formula */
  32. keylen = offsetof(msg_t, text) /* offset of last key field */
  33. + sizeof(beijing) /* size of last key field */
  34. - offsetof(msg_t, encoding); /* offset of first key field */
  35. /* add our structure to the hash table */
  36. HASH_ADD( hh, msgs, encoding, keylen, msg);
  37. /* look it up to prove that it worked :-) */
  38. msg=NULL;
  39. lookup_key = (lookup_key_t*)malloc(sizeof(*lookup_key) + sizeof(beijing));
  40. memset(lookup_key, , sizeof(*lookup_key) + sizeof(beijing));
  41. lookup_key->encoding = UTF32;
  42. memcpy(lookup_key->text, beijing, sizeof(beijing));
  43. HASH_FIND( hh, msgs, &lookup_key->encoding, keylen, msg );
  44. if (msg)
  45. printf("found /n");
  46. free(lookup_key);
  47. return ;
  48. }

uthash支持将一个结构体变量存储在不同的hash表里,并且可以指定不同的字段做为键

例如:

  1. #include <stdlib.h> /* malloc */
  2. #include <stddef.h> /* offsetof */
  3. #include <stdio.h> /* printf */
  4. #include <string.h> /* memset */
  5. #include "uthash.h"
  6.  
  7. struct my_struct
  8. {
  9. int id; /* usual key */
  10. char username[]; /* alternative key */
  11. UT_hash_handle hh1; /* handle for first hash table */
  12. UT_hash_handle hh2; /* handle for second hash table */
  13. };
  14. int main()
  15. {
  16. struct my_struct *users_by_id = NULL, *users_by_name = NULL, *s;
  17. int i;
  18. char *name;
  19. s = malloc(sizeof(struct my_struct));
  20. s->id = ;
  21. strcpy(s->username, "thanson");
  22. /* add the structure to both hash tables */
  23. HASH_ADD(hh1, users_by_id, id, sizeof(int), s);
  24. HASH_ADD(hh2, users_by_name, username, strlen(s->username), s);
  25. /* lookup user by ID in the "users_by_id" hash table */
  26. i=;
  27. HASH_FIND(hh1, users_by_id, &i, sizeof(int), s);
  28. if (s) printf("found id %d: %s/n", i, s->username);
  29. /* lookup user by username in the "users_by_name" hash table */
  30. name = "thanson";
  31. HASH_FIND(hh2, users_by_name, name, strlen(name), s);
  32. if (s)
  33. printf("found user %s: %d/n", name, s->id);
  34. return ;
  35. }

注意,若要将结构体变量存储在不同的hash表里,需要在该结构体中为每个hash表定义一个UT_hash_handle字段。

选择

简单来说,select就是在一个hash表选择出一批数据,加入到另一个hash表中。它比用HASH_ADD更快。例子如下:

  1. #include "uthash.h"
  2. #include <stdlib.h> /* malloc */
  3. #include <stdio.h> /* printf */
  4. typedef struct
  5. {
  6. int id;
  7. UT_hash_handle hh;
  8. UT_hash_handle ah;
  9. } example_user_t;
  10. #define EVENS(x) (((x)->id & 1) == 0)
  11. int evens(void *userv)
  12. {
  13. example_user_t *user = (example_user_t*)userv;
  14. return ((user->id & ) ? : );
  15. }
  16. int idcmp(void *_a, void *_b)
  17. {
  18. example_user_t *a = (example_user_t*)_a;
  19. example_user_t *b = (example_user_t*)_b;
  20. return (a->id - b->id);
  21. }
  22. int main(int argc,char *argv[])
  23. {
  24. int i;
  25. example_user_t *user, *users=NULL, *ausers=NULL;
  26. /* create elements */
  27. for(i=; i<; i++)
  28. {
  29. user = (example_user_t*)malloc(sizeof(example_user_t));
  30. user->id = i;
  31. HASH_ADD_INT(users,id,user);
  32. }
  33. for(user=users; user; user=(example_user_t*)(user->hh.next))
  34. {
  35. printf("user %d/n", user->id);
  36. }
  37. /* now select some users into ausers */
  38. HASH_SELECT(ah,ausers,hh,users,evens);
  39. HASH_SRT(ah,ausers,idcmp);
  40. for(user=ausers; user; user=(example_user_t*)(user->ah.next))
  41. {
  42. printf("auser %d/n", user->id);
  43. }
  44. return ;
  45. }

全部代码实例

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include "uthash.h"
  4.  
  5. typedef struct my_struct
  6. {
  7. int id; /* we'll use this field as the key */
  8. char name[];
  9. UT_hash_handle hh; /* makes this structure hashable */
  10. } HashNode;
  11. typedef HashNode* HashHead;
  12.  
  13. int count_user(HashHead head);
  14. HashNode *find_user(HashHead head, int user_id)
  15. {
  16. HashNode *s;
  17. HASH_FIND_INT(head, &user_id, s); /* s: output pointer */
  18. return s;
  19. }
  20. void add_user(HashHead *head, HashNode *users)
  21. {
  22. if(!find_user(*head, users->id))
  23. HASH_ADD_INT(*head, id, users);
  24. }
  25. void replace_user(HashHead *head, HashNode *newNode)
  26. {
  27. HashNode *oldNode = find_user(*head, newNode->id);
  28. if (oldNode)
  29. HASH_REPLACE_INT(*head, id, newNode, oldNode);
  30. }
  31. void delete_user(HashHead *head,HashNode *user)
  32. {
  33. if (user)
  34. {
  35. HASH_DEL(*head, user); /* user: pointer to deletee */
  36. free(user); /* optional; it's up to you! */
  37. }
  38. }
  39. void print_user(HashHead head)
  40. {
  41. HashNode *s;
  42. printf("size is %d\n", count_user(head));
  43. for (s = head; s != NULL; s = s->hh.next)
  44. {
  45. printf("user id %d, name %s\n", s->id, s->name);
  46. }
  47. }
  48. void print_user_iterator(HashHead head)
  49. {
  50. HashNode *s, *tmp;
  51. printf("size is %d\n", count_user(head));
  52. HASH_ITER(hh, head, s, tmp)
  53. {
  54. printf("user id %d: name %s\n", s->id, s->name);
  55. /* ... it is safe to delete and free s here */
  56. }
  57. }
  58. int count_user(HashHead head)
  59. {
  60. return HASH_COUNT(head);
  61. }
  62. int name_sort(HashNode *a, HashNode *b)
  63. {
  64. return strcmp(a->name,b->name);
  65. }
  66.  
  67. int id_sort(HashNode *a, HashNode *b)
  68. {
  69. return (a->id - b->id);
  70. }
  71.  
  72. void sort_by_name(HashHead *head)
  73. {
  74. HASH_SORT(*head, name_sort);
  75. }
  76.  
  77. void sort_by_id(HashHead *head)
  78. {
  79. HASH_SORT(*head, id_sort);
  80. }
  81. int main()
  82. {
  83. printf("--------------init---------------\n");
  84. HashHead head = NULL;
  85. printf("--------------add---------------\n");
  86. HashNode *node = malloc(sizeof(HashNode));
  87. node->id = ;
  88. strcpy(node->name, "tom");
  89. add_user(&head, node);
  90.  
  91. node = malloc(sizeof(HashNode));
  92. node->id = ;
  93. strcpy(node->name, "jerry");
  94. add_user(&head, node);
  95.  
  96. node = malloc(sizeof(HashNode));
  97. node->id = ;
  98. strcpy(node->name, "jack");
  99. add_user(&head, node);
  100.  
  101. node = malloc(sizeof(HashNode));
  102. node->id = ;
  103. strcpy(node->name, "zero");
  104. add_user(&head, node);
  105.  
  106. print_user(head);
  107.  
  108. printf("--------------replace---------------\n");
  109. HashNode *newNode = malloc(sizeof(HashNode));
  110. newNode->id = ;
  111. strcpy(newNode->name, "rose");
  112. replace_user(&head, newNode);
  113. print_user(head);
  114.  
  115. printf("--------------delete---------------\n");
  116. delete_user(&head, find_user(head, ));
  117. print_user(head);
  118. printf("--------------sort-by-id---------------\n");
  119. sort_by_id(&head);
  120. print_user(head);
  121. printf("--------------sort-by-name---------------\n");
  122. sort_by_name(&head);
  123. print_user(head);
  124. return ;
  125. }

C :uthash的更多相关文章

  1. 4 cocos2dx 3.0 源码分析- scheduler

    scheduler 这个类, 负责了引擎的自定义更新, 及定时更新相关的操作, 看看下面的代码,很熟悉吧.   schedule(schedule_selector(HelloWorld::updat ...

  2. java web 开发三剑客 -------电子书

    Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知In ...

  3. 所有selenium相关的库

    通过爬虫 获取 官方文档库 如果想获取 相应的库 修改对应配置即可 代码如下 from urllib.parse import urljoin import requests from lxml im ...

  4. C开源hash项目uthash

    uthash 是C的比较优秀的开源代码,它实现了常见的hash操作函数,例如查找.插入.删除等.该套开源代码采用宏的方式实现hash函数的相关功能,支持C语言的任意数据结构最为key值,甚至可以采用多 ...

  5. mysql-unsha1:在未知密码情况下,登录任意MYSQL数据库

    摘要 这个POC用于在不知道明文密码的情况下对启用了密码安全认证插件(默认开启插件:mysql_native_password)的MYSQL数据库进行登录. 前提条件为: 1.为了获取到已知用户的ha ...

  6. Cocos2d-x 源代码分析 : Scheduler(定时器) 源代码分析

    源代码版本号 3.1r,转载请注明 我也最终不out了,開始看3.x的源代码了.此时此刻的心情仅仅能是wtf! !!!!!!! !.只是也最终告别CC时代了. cocos2d-x 源代码分析文件夹 h ...

  7. 简单好用的hash表-----uthash

    在软件开发中,不可不免的会使用到hash表,hash表的优点这里就不说了,以下介绍一个hash表的C实现, uthash是用宏实现的,使用的时候非常方便,只用包含uthash.h即可. Uthash的 ...

  8. c语言——uthash使用

    参考:https://troydhanson.github.io/uthash/userguide.html https://blog.csdn.net/lovemust/article/detail ...

  9. In-Memory:内存数据库

    在逝去的2016后半年,由于项目需要支持数据的快速更新和多用户的高并发负载,我试水SQL Server 2016的In-Memory OLTP,创建内存数据库实现项目的负载需求,现在项目接近尾声,系统 ...

随机推荐

  1. python连接mysql中文数据编码

    系统是win7 x64 Python 2.7.6的site.py里面编码设定为 utf-8 py文件首行指定 #coding:utf-8 MySQL 5.5.38安装时指定代码为utf-8 peewe ...

  2. 自己模拟的ftl 用法:

    基类 public class Ftl_object_data_model { //三种基本属性 private boolean canRead=true;//是否能读取 ;//长度 private ...

  3. 【python实现卷积神经网络】上采样层upSampling2D实现

    代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...

  4. 【Java】关键字 和 保留字

    Java的关键字[Keyword]和 保留字[Reserved word] 官方描述: https://docs.oracle.com/javase/tutorial/java/nutsandbolt ...

  5. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(八)之Reusing Classes

    The trick is to use the classes without soiling the existing code. 1. composition--simply create obj ...

  6. python超实用的30 个简短的代码片段(二)

    Python是目前最流行的语言之一,它在数据科学.机器学习.web开发.脚本编写.自动化方面被许多人广泛使用. 它的简单和易用性造就了它如此流行的原因. 如果你正在阅读本文,那么你或多或少已经使用过P ...

  7. Python套接字之UDP

    目录 基于UDP的socket 发送消息 接收消息 基于UDP的socket 面向无连接的不可靠数据传输,可以没有服务器端,只不过没有服务器端,发送的数据会被直接丢弃,并不能到达服务器端 发送消息 在 ...

  8. B - How Many Equations Can You Find dfs

    Now give you an string which only contains 0, 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9.You are asked to add the sig ...

  9. JavaScript中的作用域和作用域链(边学边写)[看着别人的博客纯手敲]

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域的工作原理.今天这篇文章对JavaScript作用域和作用域链简单的介绍,希望能帮 ...

  10. 史上最详细的VM虚拟机安装Kali-linux教程(以2020.1版本为例,含下载地址+默认提升为root权限)

    一.官方下载 Kali Linux 官方网址:www.Kali.org下载方式分两种:http 下载和 bt 下载(由于是国外网站 http 方式下载会非常慢),选择对应版本点击即可下载. 二.创建新 ...