Uthash Note

本文记录 C 语言哈希表 uthash 的笔记

Introduction

由于 C 语言本身不存在哈希,我们可以调用开源的第三方头文件:uthash.h。使用 uthash 添加,查找和删除通常是常数时间的操作,此哈希的目标是简约高效。它会自动内联,因为它是作为宏实现的。 uthash 还包括三个额外的头文件,主要提供链表,动态数组和字符串。utlist.h 为提供了链接列表宏。utarray.h 使用宏实现动态数组。utstring.h 实现基本的动态字符串。

使用

定义结构体

1
2
3
4
5
6
7
8
#include "uthash.h"
struct my_struct {
    int id;                    /* key */
    char name[10];
    UT_hash_handle hh;         /* makes this structure hashable */
};
/*声明哈希为NULL指针*/
struct my_struct *users = NULL;    /* important! initialize to NULL */

这里我们将 id 作为一个索引值,也就是键值,将 name 作为 value。一定要包含UT_hash_handle hh; hh 不需要初始化。它可以命名为任何名称,但是我们一般都命名为 hh。

添加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void add_user(int user_id, char *name) {
    struct my_struct *s;
    /*重复性检查,当把两个相同key值的结构体添加到哈希表中时会报错*/
    HASH_FIND_INT(users, &user_id, s);  /* id already in the hash? */
    /*只有在哈希中不存在ID的情况下,我们才创建该项目并将其添加。否则,我们只修改已经存在的结构。*/
    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);
}

HASH_ADD_INT表示添加的键值为int类型 HASH_ADD_STR表示添加的键值为字符串类型 HASH_ADD_PTR表示添加的键值为指针类型 HASH_ADD表示添加的键值可以是任意类型。

HASH_ADD_INT函数中,第一个参数users是哈希表,第二个参数id是键字段的名称。最后一个参数s是指向要添加的结构的指针

查找

1
2
3
4
5
struct my_struct *find_user(int user_id) {
    struct my_struct *s;
    HASH_FIND_INT( users, &user_id, s );  /* s: output pointer */
    return s;
}

'HASH_FIND_INT' 第一个参数users是哈希表,第二个参数是user_id的地址(一定要传递地址)。最后s是输出变量。当可以在哈希表中找到相应键值时,s返回给定键的结构,当找不到时s返回NULL。

替换

1
2
3
4
5
void replace_user(HashHead *head, HashNode *newNode) {
    HashNode *oldNode = find_user(*head, newNode->id);
    if (oldNode)
        HASH_REPLACE_INT(*head, id, newNode, oldNode);
}

删除

1
2
3
4
void delete_user(struct my_struct *user) {
    HASH_DEL(users, user);  /* user: pointer to deletee */
    free(user);             /* optional; it's up to you! */
}

要从哈希表中删除结构,必须具有指向它的指针。(如果只有键,请先执行HASH_FIND以获取结构指针)。 删除结构只是将其从哈希表中删除,并非free 。何时释放结构的选择完全取决于自己;uthash永远不会释放您的结构

循环删除

1
2
3
4
5
6
7
8
void delete_all() {
  struct my_struct *current_user, *tmp;

  HASH_ITER(hh, users, current_user, tmp) {
    HASH_DEL(users,current_user);  /* delete; users advances to next */
    free(current_user);            /* optional- if you want to free  */
  }
}

删除哈希表所有元素

1
HASH_CLEAR(hh,users);

之后,列表头(此处为users)将设置为NULL

哈希表元素个数:

1
2
3
unsigned int num_users;
num_users = HASH_COUNT(users);
printf("there are %u users\n", num_users);

当 users 为 NULL 时,HASH_COUNT 会返回 0.

遍历哈希表中的所有项目

1
2
3
4
5
6
7
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);
    }
}

还有一个 hh.prev 指针,可用于从任何已知项开始向后迭代哈希。

排序哈希表

1
HASH_SORT( users, name_sort );

第二个参数是指向比较函数的指针。它必须接受两个指针参数(要比较的项目),并且如果第一个项目分别在第二个项目之前,等于或之后排序,则必须返回小于零,零或大于零的int。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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);
}

字符串键值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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;
}

结构体中的键值为字符串数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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;
}

结构体中的键值为字符串指针

Reference

  1. uthash user guide
updatedupdated2022-07-142022-07-14