C語言雜湊表uthash的使用方法詳解(附下載鏈接)

2020-08-08 09:13:16

工科生一枚,熱衷於底層技術開發,有強烈的好奇心,感興趣內容包括微控制器,嵌入式Linux,Uboot等,歡迎學習交流!
愛好跑步,打籃球,睡覺。
歡迎加我QQ1500836631(備註CSDN),一起學習交流問題,分享各種學習資料,電子書籍,學習視訊等。

uthash簡介

  由於C語言本身不存在雜湊,但是當需要使用雜湊表的時候自己構建雜湊會異常複雜。因此,我們可以呼叫開源的第三方標頭檔案,這只是一個頭檔案:uthash.h。我們需要做的就是將標頭檔案複製到您的專案中,然後:#include 「uthash.h」。由於uthash僅是標頭檔案,因此沒有可鏈接的庫程式碼。

  使用uthash新增,查詢和刪除通常是常數時間的操作,此雜湊的目標是簡約高效。它大約有1000行C。它會自動內聯,因爲它是作爲宏實現的。
  uthash還包括三個額外的標頭檔案,主要提供鏈表,動態陣列和字串。utlist.h爲C結構提供了鏈接列表宏。utarray.h使用宏實現動態陣列。utstring.h實現基本的動態字串。
  github下載鏈接:https://github.com/troydhanson/uthash

uthash的使用

定義結構體

  這裏我們將id作爲一個索引值,也就是鍵值,將name作爲value。

#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 */

  注意:一定要包含UT_hash_handle hh;hh不需要初始化。它可以命名爲任何名稱,但是我們一般都命名爲hh。

新增

  HASH_ADD_INT表示新增的鍵值爲int型別
  HASH_ADD_STR表示新增的鍵值爲字串型別
  HASH_ADD_PTR表示新增的鍵值爲指針型別
  HASH_ADD表示新增的鍵值可以是任意型別

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函數中,第一個參數users是雜湊表,第二個參數id是鍵欄位的名稱。最後一個參數s是指向要新增的結構的指針。

查詢

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

  在上述程式碼中,第一個參數users是雜湊表,第二個參數是user_id的地址一定要傳遞地址)。最後s是輸出變數。當可以在雜湊表中找到相應鍵值時,s返回給定鍵的結構,當找不到時s返回NULL。

替換

  HASH_REPLACE宏等效於HASH_ADD宏,HASH_REPLACE會嘗試查詢和刪除專案外。如果找到並刪除了一個專案,它還將返回該專案的指針作爲輸出參數。

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

刪除

  要從雜湊表中刪除結構,必須具有指向它的指針。(如果只有鍵,請先執行HASH_FIND以獲取結構指針)。

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

  同樣,這裏users是雜湊表,user是指向我們要從雜湊中刪除的結構的指針。

  刪除結構只是將其從雜湊表中刪除,並非free 。何時釋放結構的選擇完全取決於自己;uthash永遠不會釋放您的結構

回圈刪除

  HASH_ITER是一個宏定義,程式執行時被替換爲一個回圈。

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  */
  }
}

刪除雜湊表所有元素

  如果您只想刪除所有專案,但不釋放它們或進行每個元素的清理,則可以通過一次操作更有效地做到這一點:

HASH_CLEAR(hh,users);

  之後,列表頭(此處爲users)將設定爲NULL。

計算雜湊表元素個數

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

  當users爲NULL時,HASH_COUNT會返回0.

遍歷雜湊表中的所有專案

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指針,可用於從任何已知項開始向後迭代雜湊。
  由於hh.prev和hh.next欄位的緣故,可以在雜湊中向前和向後迭代。可以通過重複跟隨這些指針來存取雜湊中的所有專案,因此雜湊也是雙鏈表

排序雜湊表

HASH_SORT( users, name_sort );

  第二個參數是指向比較函數的指針。它必須接受兩個指針參數(要比較的專案),並且如果第一個專案分別在第二個專案之前,等於或之後排序,則必須返回小於零,零或大於零的int。 (這與標準C庫中的strcmp或qsort使用的約定相同)。

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)
   */
}

  name_sort和id_sort的兩個排序函數範例。

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);
}

完整程式碼

#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;
}

鍵值的各種型別舉例

整型鍵值

  當鍵值爲整型時,可以使用HASH_ADD_INT和HASH_FIND_INT。(對於所有型別的鍵,其他操作(例如HASH_DELETE和)HASH_SORT都是相同的)。

字串鍵值

  當鍵值爲字串時,具體要使用那個函數取決於結構體中的鍵值爲字串陣列還是字串指針。 這一點很重要。當結構體中的鍵值爲字串陣列時,使用HASH_ADD_STR。鍵值爲字串指針時使用HASH_ADD_KEYPTR。接下來給出兩個例子參考。

  當結構體中的鍵值爲字串陣列時

#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;
}

  當結構體中的鍵值爲字串指針時

#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;
}

指針鍵值

#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;
}

結構體鍵值

  在將專案新增到雜湊或查詢專案之前,必須將結構體鍵值中的元素清零。

#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;
}

常用宏參考

型別宏

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)

通用宏

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)

參數說明

  參數說明
  hh_name
  UT_hash_handle結構中欄位的 名稱。俗稱 hh。

  head
  結構指針變數,用作雜湊的「頭」。如此命名是因爲它最初指向新增到雜湊中的第一項。

  keyfield_name
  結構中鍵欄位的名稱。(對於多欄位鍵,這是鍵的第一個欄位)。如果您不熟悉宏,則將欄位名稱作爲參數傳遞似乎很奇怪。請參閱 註釋。

  key_len
  鍵欄位的長度(以位元組爲單位)。例如,對於整數鍵,它是 sizeof(int),而對於字串鍵,它是strlen(key)。(有關多欄位鍵,請參閱此註釋。)

  key_ptr
  對於HASH_FIND,這是指向要在雜湊中查詢的鍵的指針(由於它是指針,因此您不能在此處直接傳遞文字值)。對於 HASH_ADD_KEYPTR,這是要新增的項的鍵的地址。

  hashv
  提供的鍵的雜湊值。這是…_BYHASHVALUE宏的輸入參數,是 的輸出參數HASH_VALUE。如果您要重複查詢相同的鍵,則重用快取的雜湊值可以優化效能。

  item_ptr
  指向要新增,刪除,替換或查詢的結構的指針,或迭代期間的當前指針。這是一個輸入參數HASH_ADD, HASH_DELETE和HASH_REPLACE宏,和用於輸出參數HASH_FIND 和HASH_ITER。(當HASH_ITER用於迭代時,tmp_item_ptr 是與item_ptr內部使用的型別相同的另一個變數)。

  replace_item_ptr
  用於HASH_REPLACE宏。這是一個輸出參數,設定爲指向替換的專案(如果沒有替換的專案,則設定爲NULL)。

  cmp
  指向比較函數的指針,該函數接受兩個參數(指向要比較的專案的指針),並返回一個int值,該值指定第一個專案應在第二個專案之前,等於還是之後排序(如strcmp)。

  condition
  接受單個參數的函數或宏(指向結構的空指針,需要將其強制轉換爲適當的結構型別)。如果應「選擇」結構以將其新增到目標雜湊中,則函數或宏的值應爲非零值。