C言語哈希表(uthash)簡介

一、哈希表的概念及作用
        在一般的線性表或者樹中,我們所儲存的值寫它的存儲位置的關係是隨機的。因此,在查找過程中,需要一系列的與關鍵字的比較。算法的時間複雜度與比較的次數有關。線性表查找的時間複雜度爲O(n)而平衡二叉樹的查找的時間複雜度爲O(log(n))。無論是採用線程表或是樹進行存儲,都面臨面隨着數據量的增大,查找速度將不同程度變慢的問題。而哈希表正好解決了這個問題。它的主要思想是通過將值與其存儲位置相關聯,來實現快速的隨機存儲。

關於哈希表的詳細說明,可以參考以下鏈接:

http://zh.wikipedia.org/zh-cn/%E5%93%88%E5%B8%8C%E8%A1%A8

  http://en.wikipedia.org/wiki/Hash_table


二、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,而是支持MinGW。

 uthash支持:增加、查找、刪除、計數、迭代、排序、選擇等操作。

第一步:包含頭文件

  #include "uthash.h"

第二步:自定義數據結構。每個結構代表一個鍵-值對,並且,每個結構中要有一個UT_hash_handle成員。

  struct my_struct {
int id; /* key */
char name[10];
UT_hash_handle hh; /* makes this structure hashable */
};

第三步:定義hash表指針。這個指針爲前面自定義數據結構的指針,並初始化爲NULL。

  struct my_struct *users = NULL; /* important! initialize to NULL */

第四步:進行一般的操作。

增加:

  int add_user(int user_id, char *name) {
struct my_struct *s;
s = malloc(sizeof(struct my_struct));
s->id = user_id;
strcpy(s->name, name);
HASH_ADD_INT( users, id, s ); /* id: name of key field */
}

查找:

  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); /* optional; it’s up to you! */
}

計數:

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

迭代:

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

排序:

  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中,並不會對其所儲存的值進行移動或者複製,也不會進行內存的釋放。

 

 

 

 


三、uthash的高級用法
第一、關於鍵

對於uthash來說,鍵只是一系列的字節。所以,我們可以使用任意類型的鍵。包括:整形,字符串,結構等。uthash並不建議以浮點數作爲鍵。若鍵的類型是結構體,那麼在加入到hash表前,要對無關的數據進行清零。例如我們定義如下結構體:

  typedef struct { /* this structure will be our key */
char a;
int b;
} record_key_t;

由於系統會自動進行字節對齊,也就是在a後面加入3個點位的字節。所以,在將類型爲record_key_t*的數據加入到hash表前,一定要保證該數據的無關字節的值爲0,否則將可能造成存入的鍵值無法被查找到的情況。

下面是一段使用結構體作爲鍵的類型的代碼:

  

#include <stdlib.h>
#include <stdio.h>
#include "uthash.h"
typedef struct { /* this structure will be our key */
char a;
int b;
} record_key_t;
typedef struct { /* the hash is comprised of these */
record_key_t key;
/* ... other data ... */
UT_hash_handle hh;
} record_t;
int main(int argc, char *argv[]) {
record_t l, *p, *r, *records = NULL;
r = (record_t*)malloc( sizeof(record_t) );
memset(r, 0, sizeof(record_t)); /* zero fill! */
r->key.a = ’a’;
r->key.b = 1;
HASH_ADD(hh, records, key, sizeof(record_key_t), r);
memset(&l, 0, sizeof(record_t)); /* zero fill! */
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);
return 0;
}

uthash中還支持組合鍵,但是要求組合鍵的存儲位置是相鄰的。所謂的組合鍵也是利用了uthash把一切的鍵都看作是字節序列的原理,我們只要將組合鍵的第一個字段和整個組合鍵的長度正確的填入相關的函數就可以了。例子如下:

  

#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, *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);
    return 0;
}

第二、uthash支持將一個結構體變量存儲在不同的hash表裏,並且可以指定不同的字段做爲鍵。例如:

struct my_struct {
int id; /* usual key */
char username[10]; /* alternative 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);
/* lookup 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);
/* lookup 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表裏,需要在該結構體中爲每個hash表定義一個UT_hash_handle字段。

 

第三、選擇。簡單來說,select就是在一個hash表選擇出一批數據,加入到另一個hash表中。它比用HASH_ADD更快。例子如下:

#include "uthash.h"
#include <stdlib.h>   /* malloc */
#include <stdio.h>    /* printf */
typedef struct {
    int id;
    UT_hash_handle hh;
    UT_hash_handle ah;
} example_user_t;
#define EVENS(x) (((x)->id & 1) == 0)
int evens(void *userv) {
  example_user_t *user = (example_user_t*)userv;
  return ((user->id & 1) ? 0 : 1);
}
int idcmp(void *_a, void *_b) {
  example_user_t *a = (example_user_t*)_a;
  example_user_t *b = (example_user_t*)_b;
  return (a->id - b->id);
}
int main(int argc,char *argv[]) {
    int i;
    example_user_t *user, *users=NULL, *ausers=NULL;
    /* create elements */
    for(i=0;i<10;i++) {
        user = (example_user_t*)malloc(sizeof(example_user_t));
        user->id = i;
        HASH_ADD_INT(users,id,user);
    }
    for(user=users; user; user=(example_user_t*)(user->hh.next)) {
        printf("user %d/n", user->id);
    }
    /* now select some users into ausers */
    HASH_SELECT(ah,ausers,hh,users,evens);
    HASH_SRT(ah,ausers,idcmp);
    for(user=ausers; user; user=(example_user_t*)(user->ah.next)) {
        printf("auser %d/n", user->id);
    }
   return 0;
}

第四、內置hash函數。uthash支持如下幾種hash函數:

  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 (see note)

編譯代碼時,可以選擇不同的hash函數,只要在編譯時指定就可以了。例如:

  cc -DHASH_FUNCTION=HASH_BER -o program program.c

第五、線程。uthash是非線程安全的,所以如要在多線程中進行使用,必須對操作進行加鎖。例如:

首先初始化鎖

  pthread_rwlock_t lock;
if (pthread_rwlock_init(&lock,NULL) != 0) fatal("can’t create rwlock");

如果進行查找操作

  if (pthread_rwlock_rdlock(&lock) != 0) fatal("can’t get rdlock");
HASH_FIND_INT(elts, &i, e);
pthread_rwlock_unlock(&lock);

如果進行刪除操作

  if (pthread_rwlock_wrlock(&lock) != 0) fatal("can’t get wrlock");
HASH_DEL(elts, e);
pthread_rwlock_unlock(&lock);

 
————————————————
版權聲明:本文爲CSDN博主「Super_HQ」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/hongqun/article/details/6103275

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章