技术标签: 数据结构(堆栈-队列-链表-哈希表等) CHash uthash C哈希 hash 哈希表
[外链图片转存失败(img-27kNHnPB-1565700105635)(https://note.youdao.com/yws/api/personal/file/8890D964F4D7432E98496BC35E03362A?method=download&shareKey=65db05cc5d9acbc5b23a3f8a176164e0)]
uthash是C的比较优秀的开源代码,它实现了常见的hash操作函数,例如查找、插入、删除等待。该套开源代码采用宏的方式实现hash函数的相关功能,支持C语言的任意数据结构最为key值,甚至可以采用多个值作为key,无论是自定义的struct还是基本数据类型,需要注意的是不同类型的key其操作接口方式略有不通。官方原文说明如下:
Any C structure can be stored in a hash table using uthash. Just add a UT_hash_handle to the structure and choose one or more fields in your structure to act as the key. Then use these macros to store, retrieve or delete items from the hash table.
使用uthash代码时只需要包含头文件"uthash.h"即可。由于该代码采用宏的方式实现,所有的实现代码都在uthash.h文件中,因此只需要在自己的代码中包含该头文件即可。
通过uthash软件,支持对hash表的item进行如下操作:
uthash的插入、查找、删除的操作时间都是常量,当然这个常量的值受到key以及所选择的hash函数的影响。
uthash共提供了7种函数函数,一般情况下选择默认的即可。如果对效率要求特别高时,可以再根据自己的需求选择适合自己的hash函数。
官方GitHub地址是:
uthash的英文使用文档介绍可从下面网址获得:
http://troydhanson.github.io/uthash/
http://troydhanson.github.io/uthash/userguide.html
接下来介绍uthash的使用,大部分都是来自于如上链接的翻译,部分加入了自己使用过程中的总结。
英语能力有限,翻译不对的地方多多谅解,也可通过[email protected]联系我。
在自己的数据结构中添加UT_hash_handle的支持:
#include "uthash.h"
struct my_struct {
int id; /* we'll use this field as the key */
char name[10];
UT_hash_handle hh; /* makes this structure hashable */
};
struct my_struct *g_users = NULL;
注意:在uthash中,当您的结构添加到hash表中时,并不会被移动或拷贝到其他位置。这意味着在程序运行过程中,无论是对hash表进行添加或删除操作,都可以保证您数据结构的安全性。
struct my_struct *find_user(int user_id)
{
struct my_struct *s = NULL;
HASH_FIND_INT(g_users, &user_id, s);
return s;
}
void add_user(struct my_struct *s)
{
HASH_ADD_INT(g_users, id, s );
}
void delete_user(struct my_struct *user)
{
HASH_DEL(g_users, user);
}
在实际操作时,需要特别注意以下三点:
基于第二章定义的数据结构进行介绍:
#include "uthash.h"
struct my_struct {
int id; /* we'll use this field as the key */
char name[10];
UT_hash_handle hh; /* makes this structure hashable */
};
必须将你的结构体指针初始化为空指针:
struct my_struct *g_users = NULL; /* important! initialize to NULL */
void add_user(int user_id, char *name)
{
struct my_struct *s;
HASH_FIND_INT(g_users, &user_id, s);
if(NULL == s)
{
s = malloc(sizeof(struct my_struct));
s->id = user_id;
HASH_ADD_INT(g_users, id, s ); /* id: name of key field */
}
strcpy(s->name, name);
}
一旦结构的item添加到了hash表,就不要试图去修改对应item的key值,除非你已经把它从hash表删除。
如果我们想将g_users的全局链表暴露给应用者来制定,从而可以在一个应用中使用多个链表,那需要注意正确的使用是采用二级指针(因为宏修改的是指针本身,而不仅仅是它所指向的内容):
void add_user(struct my_struct **users, int user_id, char *name)
{
...
HASH_ADD_INT( *users, id, s );
}
HASH_REPLACE宏等价于HASH_ADD宏,只是它们首先尝试查找和删除项。如果它发现并删除一个项,它还将返回该项指针作为输出参数。
struct my_struct *find_user(int user_id)
{
struct my_struct *s;
HASH_FIND_INT(g_users, &user_id, s ); /* s: output pointer */
return s;
}
中间的参数是指向key的指针。
void delete_user(struct my_struct *user)
{
HASH_DEL(g_users, user); /* user: pointer to deletee */
free(user); /* optional; it's up to you! */
}
g_users是hash表,user指向用户自己的结构体。
注意:uthash永远不会去释放使用者的结构体。HASH_DEL只是从hash表中删除节点,但并不会帮使用者释放结构体所占用的内存。
HASH_ITER宏是一个删除安全的迭代构造,它扩展为一个简单的for循环。
void delete_all()
{
struct my_struct *current_user, *tmp;
HASH_ITER(hh, g_users, current_user, tmp)
{
HASH_DEL(users,current_user); /* delete; users advances to next */
free(current_user); /* optional- if you want to free */
}
}
如果你仅仅想删除hash链表中的所有item节点,而不对item节点所占内存进行释放(或者不需要进行释放),则可以使用如下宏:
HASH_CLEAR(hh, g_users);
需要注意:HASH_CLEAR宏不会释放使用者结构体,并将hash表头(这里是g_users)设置为NULL。
unsigned int num_users;
num_users = HASH_COUNT(g_users);
printf("there are %u users\n", num_users);
当hash表g_users为NULL时,将得到0。
你可以通过hh.next指针遍历hash表中所有items。
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);
}
}
如果要循环删除item和释放结构体的话,上面的例子并不安全(因为s是临时变量,每次遍历都会改变)。当然,你可以通过定义一个临时变量用于保存s->hh.next取到的item。由于这种情况比较常见,uthash作者定义了一个HASH_ITER宏来简化这种情况的使用:
struct my_struct *s, *tmp;
HASH_ITER(hh, g_users, s, tmp) {
printf("user id %d: name %s\n", s->id, s->name);
/* ... it is safe to delete and free s here */
}
默认情况下,hash表的顺序与你插入的顺序是一致的。你可以通过HASH_SORT宏对整个hash表进行排序:
HASH_SORT(g_users, name_sort);
name_sort是你自己提供的比较算法。他必须接受两个items作为参数,且返回值为int,返回值规则与标准C的strcmp和qsort一致:小于时返回负数,相等返回0,大于则返回正数。
int sort_function(void *a, void *b)
{
/* compare a to b (cast a and b appropriately)
* return (int) -1 if (a < b)
* return (int) 0 if (a == b)
* return (int) 1 if (a > b)
*/
}
下面是两个排序的实例:
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.h的完整实例,你可以这样使用(请将example.c和uthash.h放在同一个位置):
cc -o example example.c
./example
接下来是完整的源码:
// 详见官方代码tests/example.c
#include <stdio.h> /* gets */
#include <stdlib.h> /* atoi, malloc */
#include <string.h> /* strcpy */
#include "uthash.h"
struct my_struct {
int id; /* key */
char name[10];
UT_hash_handle hh; /* makes this structure hashable */
};
struct my_struct *users = NULL;
void add_user(int user_id, char *name) {
struct my_struct *s;
HASH_FIND_INT(users, &user_id, s); /* id already in the hash? */
if (s==NULL) {
s = (struct my_struct *)malloc(sizeof *s);
s->id = user_id;
HASH_ADD_INT( users, id, s ); /* id: name of key field */
}
strcpy(s->name, name);
}
struct my_struct *find_user(int user_id) {
struct my_struct *s;
HASH_FIND_INT( users, &user_id, s ); /* s: output pointer */
return s;
}
void delete_user(struct my_struct *user) {
HASH_DEL(users, user); /* user: pointer to deletee */
free(user);
}
void delete_all() {
struct my_struct *current_user, *tmp;
HASH_ITER(hh, users, current_user, tmp) {
HASH_DEL(users, current_user); /* delete it (users advances to next) */
free(current_user); /* free it */
}
}
void print_users() {
struct my_struct *s;
for(s=users; s != NULL; s=(struct my_struct*)(s->hh.next)) {
printf("user id %d: name %s\n", s->id, s->name);
}
}
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);
}
int main(int argc, char *argv[]) {
char in[10];
int id=1, running=1;
struct my_struct *s;
unsigned num_users;
while (running) {
printf(" 1. add user\n");
printf(" 2. add/rename user by id\n");
printf(" 3. find user\n");
printf(" 4. delete user\n");
printf(" 5. delete all users\n");
printf(" 6. sort items by name\n");
printf(" 7. sort items by id\n");
printf(" 8. print users\n");
printf(" 9. count users\n");
printf("10. quit\n");
gets(in);
switch(atoi(in)) {
case 1:
printf("name?\n");
add_user(id++, gets(in));
break;
case 2:
printf("id?\n");
gets(in); id = atoi(in);
printf("name?\n");
add_user(id, gets(in));
break;
case 3:
printf("id?\n");
s = find_user(atoi(gets(in)));
printf("user: %s\n", s ? s->name : "unknown");
break;
case 4:
printf("id?\n");
s = find_user(atoi(gets(in)));
if (s) delete_user(s);
else printf("id unknown\n");
break;
case 5:
delete_all();
break;
case 6:
sort_by_name();
break;
case 7:
sort_by_id();
break;
case 8:
print_users();
break;
case 9:
num_users=HASH_COUNT(users);
printf("there are %u users\n", num_users);
break;
case 10:
running=0;
break;
}
}
delete_all(); /* free any structures */
return 0;
}
本节介绍如何在不同类型的key下使用uthash。你可以使用的key类型包括:integers、strings、pointers、structures等。
注意:你可以使用float或double作为key,但由于精度的问题,uthash可能将两个非常相近的两个key认为是同一个值。
当你的key是整型时,可以直接使用HASH_ADD_INT、HASH_FIND_INT操作,HASH_DELETE与HASH_SORT对所有类型key都是一样的。
如果您定义的结构体key的类型是string,那么使用的方法依赖于你定义key的方法:指针的方式(char *)还是数组的方式(char a[10])。这个差异是非常重要的。当你的结构体使用的是指针方式,那么你应当使用HASH_ADD_KEYPTR方法;当你使用的数组方式,则需要使用HASH_ADD_STR方法。
实际上数组方式的char [10],key是存储在结构体内部的;而指针方式的char *,key是存储在结构体外部的。因此char [10]应该使用HASH_ADD_STR,而char *应该使用HASH_ADD_KEYPTR。
下面是一个数组形式的hash实例(对应tests/test15.c):
#include <string.h> /* strcpy */
#include <stdlib.h> /* malloc */
#include <stdio.h> /* printf */
#include "uthash.h"
struct my_struct {
char name[10]; /* key (string is WITHIN the structure) */
int id;
UT_hash_handle hh; /* makes this structure hashable */
};
int main(int argc, char *argv[]) {
const char *names[] = { "joe", "bob", "betty", NULL };
struct my_struct *s, *tmp, *users = NULL;
for (int i = 0; names[i]; ++i) {
s = (struct my_struct *)malloc(sizeof *s);
strcpy(s->name, names[i]);
s->id = i;
HASH_ADD_STR( users, name, s );
}
HASH_FIND_STR( users, "betty", s);
if (s) printf("betty's id is %d\n", s->id);
/* free the hash table contents */
HASH_ITER(hh, users, s, tmp) {
HASH_DEL(users, s);
free(s);
}
return 0;
}
编译执行,输入结构:
betty's id is 2
若将上面实例修改为指针方式:
#include <string.h> /* strcpy */
#include <stdlib.h> /* malloc */
#include <stdio.h> /* printf */
#include "uthash.h"
struct my_struct {
const char *name; /* key */
int id;
UT_hash_handle hh; /* makes this structure hashable */
};
int main(int argc, char *argv[]) {
const char *names[] = { "joe", "bob", "betty", NULL };
struct my_struct *s, *tmp, *users = NULL;
for (int i = 0; names[i]; ++i) {
s = (struct my_struct *)malloc(sizeof *s);
s->name = names[i];
s->id = i;
HASH_ADD_KEYPTR( hh, users, s->name, strlen(s->name), s );
}
HASH_FIND_STR( users, "betty", s);
if (s) printf("betty's id is %d\n", s->id);
/* free the hash table contents */
HASH_ITER(hh, users, s, tmp) {
HASH_DEL(users, s);
free(s);
}
return 0;
}
详见实例:tests/test40.c
你可以使用指针(地址)作为key,也就是说指针(地址)本身也可以作为key来使用。也对应HASH_ADD_KEYPTR相关方法。下面是一个实例:
#include <stdio.h>
#include <stdlib.h>
#include "uthash.h"
typedef struct {
void *key;
int i;
UT_hash_handle hh;
} el_t;
el_t *hash = NULL;
char *someaddr = NULL;
int main() {
el_t *d;
el_t *e = (el_t *)malloc(sizeof *e);
if (!e) return -1;
e->key = (void*)someaddr;
e->i = 1;
HASH_ADD_PTR(hash,key,e);
HASH_FIND_PTR(hash, &someaddr, d);
if (d) printf("found\n");
/* release memory */
HASH_DEL(hash,e);
free(e);
return 0;
}
详见tests/test57.c。
你也可以指定你的key为自定义的结构体,这对于uthash来说,只是一些列连续的bytes值。因此,即使是嵌套的结构,也可以作为key来使用。我们将使用通用的HASH_ADD和HASH_FIND来使用hash。下面是使用结构体的一个实例:
#include <stdlib.h>
#include <stdio.h>
#include "uthash.h"
typedef struct {
char a;
int b;
} record_key_t;
typedef struct {
record_key_t key;
/* ... other data ... */
UT_hash_handle hh;
} record_t;
int main(int argc, char *argv[]) {
record_t l, *p, *r, *tmp, *records = NULL;
r = (record_t *)malloc(sizeof *r);
memset(r, 0, sizeof *r);
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);
HASH_ITER(hh, records, p, tmp) {
HASH_DEL(records, p);
free(p);
}
return 0;
}
你可以使用结构体中连续域的成员作为组合key。下面是multi-field key的例子:
#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, *tmp, *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);
HASH_ITER(hh, msgs, msg, tmp) {
HASH_DEL(msgs, msg);
free(msg);
}
return 0;
}
如果使用多字段key,编译器会填充相邻字段(通过在他们之间插入未使用的空间),以满足每个字段的对其要求。例如,一个结构包含一个char后面跟一个int,通常在char之后插入3个“浪费”的填充字节,以便使int字段是开始于4字节对齐的地址。
计算组合key的长度方法:如前面所述,在使用组合(多字段)key时,必须包含编译器为对齐目的添加的中间填充结构。那么key就不是结构体所显示声明的长度了。一个简单的计算组合key长度的方法是使用<stddef.h>中的offsetof宏:
key length = offsetof(last_key_field)
+ sizeof(last_key_field)
- offsetof(first_key_field)
在处理组合key时,必须在HASH_ADD和HASH_FIND之前对结构体进行0填充。
多层次hash表意味着hash表的每个元素都可能包含自己的辅助hash表,这可以是任意数量的级别。下面是多级hash表的伪代码:
$items{bob}{age} = 37
上面的含义是有一个名为items的hash表,包含bob元素,bob元素也是一个hash表,包含age元素,并且age元素的值为37。下面是一个多层次hash表的实例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "uthash.h"
/* hash of hashes */
typedef struct item {
char name[10];
struct item *sub;
int val;
UT_hash_handle hh;
} item_t;
item_t *items=NULL;
int main(int argc, char *argvp[]) {
item_t *item1, *item2, *tmp1, *tmp2;
/* make initial element */
item_t *i = malloc(sizeof(*i));
strcpy(i->name, "bob");
i->sub = NULL;
i->val = 0;
HASH_ADD_STR(items, name, i);
/* add a sub hash table off this element */
item_t *s = malloc(sizeof(*s));
strcpy(s->name, "age");
s->sub = NULL;
s->val = 37;
HASH_ADD_STR(i->sub, name, s);
/* iterate over hash elements */
HASH_ITER(hh, items, item1, tmp1) {
HASH_ITER(hh, item1->sub, item2, tmp2) {
printf("$items{%s}{%s} = %d\n", item1->name, item2->name, item2->val);
}
}
/* clean up both hash tables */
HASH_ITER(hh, items, item1, tmp1) {
HASH_ITER(hh, item1->sub, item2, tmp2) {
HASH_DEL(item1->sub, item2);
free(item2);
}
HASH_DEL(items, item1);
free(item1);
}
return 0;
一个结构体item对象,可以被添加到多个hash表中。比如下面的情况:
这种情况下,你只需要在你的结构体里面,为每个hash表定义UT_hash_handle字段即可:
UT_hash_handle hh1, hh2;
你可能需要用到这样的组合hash表:每个item都有多个key,每个key对应到不同的hash表。实现方法就是为每个不同的key添加对应的UT_hash_handle域,每个UT_hash_handle对应不同的hash表:
struct my_struct {
int id; /* first key */
char username[10]; /* second key */
UT_hash_handle hh1; /* handle for first hash table */
UT_hash_handle hh2; /* handle for second hash table */
};
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);
/* find 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);
/* find 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);
当然,也可以是两个hash表使用相同的key,比如上面的hh1和hh2都使用id作为key。
如果你想维护一个有序的hash表,你有两个选择:
int name_sort(struct my_struct *a, struct my_struct *b)
{
return strcmp(a->name,b->name);
}
HASH_ADD_KEYPTR_INORDER(hh, items, &item->name, strlen(item->name), item, name_sort);
基于一个struct结构可以保存在多个hash表(3.3.4),那么针对相同的struct结构对象,可以拥有完全不同的排序方法。比如3.3.4节的例子,可以分别按照id或name进行排序:
int sort_by_id(struct my_struct *a, struct my_struct *b)
{
if (a->id == b->id) return 0;
return (a->id < b->id) ? -1 : 1;
}
int sort_by_name(struct my_struct *a, struct my_struct *b)
{
return strcmp(a->username,b->username);
}
HASH_SRT(hh1, users_by_id, sort_by_id);
HASH_SRT(hh2, users_by_name, sort_by_name);
默认情况下,Bloom过滤器是关闭的,可以通过在编译的时候指定-DHASH_BLOOM=n即可打开Bloom过滤器:
-DHASH_BLOOM=27
其取值范围为0~32,它决定了过滤器使用的内存大小。内存使用的越多,过滤器计算将会更加准确,效率也会更高,不过也需要根据你自身系统资源来确定。
n | Bloom filter size(per hash table) |
---|---|
16 | 8K bytes |
20 | 128K bytes |
24 | 2M bytes |
28 | 32M bytes |
32 | 512M bytes |
Bloom过滤器只是一个性能特性,它不会影像任何hash表操作方式的结果。你可以在你的系统中,通过设置不同的值,进行测试对比性能提升的效率。
本操作将筛选满足条件的源hash表元素插入到目标hash表中,这不会从源hash表中删除任何元素,而是两个 hash表中有相同的结构体对象。因此,要满足此操作,struct结构体中至少应该有两个或多个hash句柄UT_hash_handle:
user_t *users=NULL, *admins=NULL; /* two hash tables */
typedef struct {
int id;
UT_hash_handle hh; /* handle for users hash */
UT_hash_handle ah; /* handle for admins hash */
} user_t;
那么我们想筛选id小于1024的用户到admins链表:
#define is_admin(x) (((user_t*)x)->id < 1024)
HASH_SELECT(ah,admins,hh,users,is_admin);
当我们提供的筛选条件永远为true,则HASH_SELECT本质上就是将两个hash表进行合并。有关HASH_SELECT实例详见tests/test36.c
当你调用HASH_FIND(hh, head, intfiled, sizeof(int), out)时,uthash将首先调用HASH_FUNCTION(intfiled, sizeof(int), hashvalue)来确定搜索的bucket b;然后,对于bucket b的每个元素elt,uthash将计算:
elt->hh.hashv == hashvalue && elt.hh.keylen == sizeof(int) && HASH_KEYCMP(intfield, elt->hh.key, sizeof(int)) == 0
在找到匹配的elt时,HASH_KEYCMP应该返回0,否则就表示要继续进行搜索。
默认情况下,uthash定义memcmp为HASH_KEYCMP宏。针对不同的平台,你可以提供你自己的memcmp方法实现:
#undef HASH_KEYCMP
#define HASH_KEYCMP(a,b,len) bcmp(a,b,len)
需要自己提供key比较方法的另外一种情况是:你所定义的key无法通过通用的比较方法实现,这种情况下,你还需要提供你自己的HASH_FUNCTION:
struct Key {
short s;
/* 2 bytes of padding */
float f;
};
/* do not compare the padding bytes; do not use memcmp on floats */
unsigned key_hash(struct Key *s) { return s + (unsigned)f; }
bool key_equal(struct Key *a, struct Key *b) { return a.s == b.s && a.f == b.f; }
#define HASH_FUNCTION(s,len,hashv) (hashv) = key_hash((struct Key *)s)
#define HASH_KEYCMP(a,b,len) (!key_equal((struct Key *)a, (struct Key *)b))
需要自己提供key比较函数的另一种情况是:有更高的性能要求或者更高的正确性要求!在对bucket线性搜索过程中,uthash总是比较32位的hashv,并且只在hashv相等时才调用HASH_KEYCMP。也就是说每次成功的查询至少有一次调用HASH_KEYCMP。在一个好的hash函数中,我们希望hasv比较仅在40亿次中产生一次“false positive”等式,因此,我们希望HASH_KEYCMP在大多数情况下生成0。如果我们期望许多成功的发现,并且我们的应用不关心偶然出现“false positive”,我们可以替换一个no-op比较函数:
#undef HASH_KEYCMP
#define HASH_KEYCMP(a,b,len) 0 /* occasionally wrong, but very fast */
注意:全局均衡比较函数HASH_KEYCMP与作为参数传递给HASH_ADD_INORDER的小于比较函数没有任何关系。
在uthash内部,hash函数会自动将key转换为bucket号以便于Jenkins使用,使用者可以完全不用关心。
你可以通过在编译时指定-DHASH_FUNCTION=HASH_xyz来使用另外的hash函数。xyz是下面表中的一种:
cc -DHASH_FUNCTION=HASH_BER -o program program.c
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 V3 |
为了您能正常的使用Convenience macros,必须保证如下两点:
macro | arguments |
---|---|
HASH_ADD_INT | (head, keyfield_name, item_ptr) |
HASH_REPLACE_INT | (head, keyfiled_name, item_ptr,replaced_item_ptr) |
HASH_FIND_INT | (head, key_ptr, item_ptr) |
HASH_ADD_STR | (head, keyfield_name, item_ptr) |
HASH_REPLACE_STR | (head,keyfield_name, item_ptr, replaced_item_ptr) |
HASH_FIND_STR | (head, key_ptr, item_ptr) |
HASH_ADD_PTR | (head, keyfield_name, item_ptr) |
HASH_REPLACE_PTR | (head, keyfield_name, item_ptr, replaced_item_ptr) |
HASH_FIND_PTR | (head, key_ptr, item_ptr) |
HASH_DEL | (head, item_ptr) |
HASH_SORT | (head, cmp) |
HASH_COUNT | (head) |
当您结构的UT_hash_handle成员名字不是hh,或者结构的key类型不是int、char []或者pointer类型时,可以使用下面通用的宏:
macro | arguments |
---|---|
HASH_ADD | (hh_name, head, keyfield_name, key_len, item_ptr) |
HASH_ADD_BYHASHVALUE | (hh_name, head, keyfield_name, key_len, hashv, item_ptr) |
HASH_ADD_KEYPTR | (hh_name, head, key_ptr, key_len, item_ptr) |
HASH_ADD_KEYPTR_BYHASHVALUE | (hh_name, head, key_ptr, key_len, hashv, item_ptr) |
HASH_ADD_INORDER | (hh_name, head, keyfield_name, key_len, item_ptr, cmp) |
HASH_ADD_BYHASHVALUE_INORDER | (hh_name, head, keyfield_name, key_len, hashv, item_ptr, cmp) |
HASH_ADD_KEYPTR_INORDER | (hh_name, head, key_ptr, key_len, item_ptr, cmp) |
HASH_ADD_KEYPTR_BYHASHVALUE_INORDER | (hh_name, head, key_ptr, key_len, hashv, item_ptr, cmp) |
HASH_REPLACE | (hh_name, head, keyfield_name, key_len, item_ptr, replaced_item_ptr) |
HASH_REPLACE_BYHASHVALUE | (hh_name, head, keyfield_name, key_len, hashv, item_ptr, replaced_item_ptr) |
HASH_REPLACE_INORDER | (hh_name, head, keyfield_name, key_len, item_ptr, replaced_item_ptr, cmp) |
HASH_REPLACE_BYHASHVALUE_INORDER | (hh_name, head, keyfield_name, key_len, hashv, item_ptr, replaced_item_ptr, cmp) |
HASH_FIND | (hh_name, head, key_ptr, key_len, item_ptr) |
HASH_FIND_BYHASHVALUE | (hh_name, head, key_ptr, key_len, hashv, item_ptr) |
HASH_DELETE | (hh_name, head, item_ptr) |
HASH_VALUE | (key_ptr, key_len, hashv) |
HASH_SRT | (hh_name, head, cmp) |
HASH_CNT | (hh_name, head) |
HASH_CLEAR | (hh_name, head) |
HASH_SELECT | (dst_hh_name, dst_head, src_hh_name, src_head, condition) |
HASH_ITER | (hh_name, head, item_ptr, tmp_item_ptr) |
HASH_OVERHEAD | (hh_name, head) |
【参数说明】:
想了解更多有关嵌入式Linux驱动、应用、常用开源库、框架、模式、重构等相关相关的主题内容,请长安下面图片,关注我的公众号(只有技术干货分享,不拉稀摆带!)。
[外链图片转存失败(img-RW5uaGUD-1565700105638)(https://note.youdao.com/yws/api/personal/file/C896F98003CB4B89BC0D38CF42CED1D3?method=download&shareKey=f9732570d98f6d2e5a417c9941e35533)]
文章浏览阅读137次。ssm整合一.applicationContext.xml1.配置数据源2.配置mybatis的sqlSessionFactory工厂//引用数据源//配置类的别名classpath:cn/sxt/mapper/UserMapper.xml34.5.<tx:advice id=“txAdvice” transaction-manag..._进行ssm整合时相关配置文件如何处理?
文章浏览阅读92次。JSF的一个核心就是事件与监听。JSF事件分为以下几种: 1、Value-change events(值改变事件) < h:inputText valueChangeListener ="#{myForm.processValueChanged..._jsf valuechangeevent
文章浏览阅读772次,点赞5次,收藏6次。CMMI,全称为Capability Maturity Model Integration,即能力成熟度模型集成。它是由美国卡内基梅隆大学软件工程研究所研发的过程改进模型,也是国际上用于评价软件企业能力成熟度的一项重要标准。CMMI的目的是帮助软件企业对软件工程过程进行管理和改进,增强开发与改进能力,从而能按时地、不超预算地开发出高质量的软件。
文章浏览阅读3.2k次。本人使用的是Codeer-Software/Selenium.CefSharp.Driver (github.com)k_cef 支付宝自动支付
文章浏览阅读2.1k次。今天升级SpringBoot的版本,然后启动的时候懵逼了,报了个错:1234567891011121314 Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error c..._启动报错compositehealthcontributor
文章浏览阅读332次。文章:AirVO: An Illumination-Robust Point-Line Visual Odometry作者:Kuan Xu, Yuefan Hao , Shenghai Yuan , Chen Wang , Lihua Xie编辑:点云PCL代码:https://github.com/sair-lab/AirVO.git来源:arXiv2023欢迎各位加入知识星球,获取PDF论文,..._airvo: an illumination-robust point-line visual odometry
文章浏览阅读1.1k次,点赞25次,收藏25次。下拉刷新是一种用户在页面顶部向下滑动时触发的事件,通常用于实现页面的数据更新或重新加载。上拉触底是一种用户在页面底部向上滑动时触发的事件,通常用于实现分页加载更多数据。生命周期是指一个小程序从被创建到被销毁的整个过程。在这个过程中,小程序会经历不同的阶段和事件,开发者可以通过生命周期函数来执行相应的逻辑操作。生命周期函数是在特定时机会被自动触发的函数,开发者可以在这些函数中编写相应的逻辑代码。在小程序中,生命周期函数包括应用生命周期函数和页面生命周期函数。
文章浏览阅读207次。三维高密度电法_三维高密度电法如何布置电极
文章浏览阅读230次。莆田学院C语言程序设计模拟试卷莆田学院《C语言程序设计》模拟试卷 - 02-(考试时间120分钟)一、单项选择题()在C语言中,用户能使用的正确标识符是【1】 。A) 5f B) _for C) struct D) _f.52、以下【2】是正确的C语言常量。A) 0678 B) '\' C) 1.2E3.5 D) 123L3、以下程序的运行结果是什么【..._以下 那个是正确的c语言常量。 a) 0678 b) '\0101' c) 1.2e3.5 d) 123l
文章浏览阅读4.8k次。opencv常用的样色空间包括RGB, HSV和YUV等。RGB颜色空间是基于三基色原理二形成的,常用于图像显示系统中;HSV描述的色度,饱和度,亮度这些表示颜色得方法,常用于描述色彩变化;YUV是通过亮度和色度来描述颜色,色度由UV通道组合而成。opencv提供cvtColor(inputArray src, outputArray dst, int code, int dstCn = 0)_opencv c语言颜空间的转换(bgr->hsv,bgr->xyz,bgr->ycrcb)
文章浏览阅读828次。【导语】自考计算机实践难考吗?对于初次报考自学考试的自考生很想知道自考计算机实践课难不难。因此,重庆自学考试网整理了自考计算机实践科的内容,希望对考生有所帮助。自学考试的实践环节,包括《计算机应用基础》的上机考核,这个是公共课,专科段的任何专业都需要考,主要是考试计算机的基础操作。考试时间一般是45分钟,在考试之前在计算机前面练习练习就没有问题了。另外,像设计类的专业,可能需要提交一个毕业设计,这..._自考计算机实践课好过吗
文章浏览阅读201次。本节学习目的1)分析Linux中的OSS声卡系统2)移植wm9876声卡3)使用madplay应用程序播放mp31.声音三要素采样频率音频采样率是指录音设备在一秒钟内对声音信号的采样次数, 常用的采样率有:8KHz - 电话所用采样率, 对于人的说话已经足够清除22.05KHz- 无线电广播所用采样率32KHz - min..._linux麦克风驱动移植