数据结构 -- 散列表
散列表作為一種能夠提供高效插入,查找,刪除 以及遍歷的數據結構,被應用在很多不同的存儲組件之中。
就像rocksdb中的hashskiplist,redis的有序集合,java的 LinkedHashMap 等 都是一些非常有特色的核心數據結構,來提供線上高效的數據操作能力。
本節對工業級散列表的基本實現 探索一番,希望加深自己對存儲產品設計理念的理解。
工業級的散列表需要具有如下能力:
-
初始大小
散列表的初始大小,剛開始的時候需要擁有一定量的存儲空間,根據實際應用情況可以通過設置散列表的初始大小,從而減少動態擴容的次數,依次提升性能。 -
裝載因子 和 動態擴容
最大裝載因子默認是0.75, 即散列表中已存儲的元素個數達到了總大小的0.75,則開始擴容 -
散列沖突的解決
根據實際情況選擇通用的兩種方案: 開放尋址法 和 鏈表法
開放尋址法:使用數組進行底層存儲,出現沖突時重新探測數據中的空閑位置,進一步插入,該方法能夠利用CPU緩存功能進行加速,但是比較耗費內存空間。
基本過程如下:
插入key3的時候hash函數計算的散列值也為3,和key2的散列值沖突,那么將向key2之后插入,但是發現key2之后沒有空間了,則跳到數據開頭重新遍歷找到第一個空閑的位置插入。鏈表法:將相同散列值的元素放入到相同的槽位,每一個槽位用鏈表管理相同hash值的元素
該方法能夠高效利用內存(鏈表節點生成新節點的時候才會分配空間),只是對CPU緩存不太友好,地址之間并不是連續的,CPU緩存基本不能生效。(這里可以通過一些有序的數據結構進行優化-- 跳表和紅黑樹)
插入的key3有和key1相同的散列值,則將key3直接插入到key1對應的bucket鏈表末尾,實際過程需要有序,所以這里插入到hashtab[2]的時候還需要找到對應的鏈表節點前驅。
-
散列函數
散列函數的設計不追求復雜,但是需要高效,計算但散列值要分布均勻。
java的LinkedHashMap的散列函數設計如下:int hash(Object key) {int h = key.hashCode();return (h ^ (h >>> 16)) & (capitity -1); //capicity表示散列表的大小 }其中,hashCode()返回的是Java對象的hash code。比如String類型的對象的hashCode()就是下面這樣:
public int hashCode() {int var1 = this.hash;if(var1 == 0 && this.value.length > 0) {char[] var2 = this.value;for(int var3 = 0; var3 < this.value.length; ++var3) {var1 = 31 * var1 + var2[var3];}this.hash = var1;}return var1; }設計的過程中盡可能保證數據的隨機性,就像手機號的后四位 一般是隨機均勻分布,這樣取用數據的過程即可作為hash函數。
通過以上四點的設計,我們基本能夠完成一個工業級的散列表實現,再做一個總結,工業級的散列表的特性:
- 支持快速的查詢、插入、刪除操作;
- 內存占用合理,不能浪費過多的內存空間;
- 性能穩定,極端情況下,散列表的性能也不會退化到無法接受的情況
工業級散列表的設計實現思路:
- 設計一個合適的散列函數;
- 定義裝載因子閾值,并且設計動態擴容策略
- 選擇合適的散列沖突解決方法
通過以上設計,使用C語言編寫一個簡單的工業級散列表實現如下,散列沖突是通過鏈表解決的
listhash.h
#ifndef __HASHTAB_H__
#define __HASHTAB_H__typedef struct _hashtab_node
{void * key;void * data;struct _hashtab_node *next;
}hashtab_node;typedef struct _hashtab
{hashtab_node **htables; /*哈希桶*/int size; /*哈希桶的最大數量*/int nel; /*哈希桶中元素的個數*/int (*hash_value)(struct _hashtab *h,const void *key); /*哈希函數*/int (*keycmp)(struct _hashtab*h,const void *key1,const void *key2);/*哈希key比較函數,當哈希數值一致時使用*/void (*hash_node_free)(hashtab_node *node);
}hashtab;#define HASHTAB_MAX_NODES (0xffffffff)typedef int (*hash_key_func)(struct _hashtab *h,const void *key); /*哈希函數*/
typedef int (*keycmp_func)(struct _hashtab*h,const void *key1,const void *key2);/*哈希key比較函數,當哈希數值一致時使用*/
typedef void (*hash_node_free_func)(hashtab_node *node);
/*根據當前結構體元素的地址,獲取到結構體首地址*/
#define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container(ptr,type,member) ({\const typeof( ((type *)0)->member) *__mptr = (ptr);\(type *) ( (char *)__mptr - offsetof(type,member));})hashtab * hashtab_create(int size,hash_key_func hash_value,keycmp_func keycmp,hash_node_free_func hash_node_free);
void *hashtab_expand(hashtab*h);
void hashtab_destory(hashtab *h);
int hashtab_insert(hashtab * h,void *key,void *data);
hashtab_node *hashtab_delete(hashtab *h, void *key);
void *hashtab_search(hashtab*h,void *key);#endif
listhash.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include"listhash.h"/* 創建 */
hashtab *hashtab_create(int size,hash_key_func hash_value,keycmp_func keycmp,hash_node_free_func hash_node_free)
{hashtab *h = NULL;int hash_num = 0; // 初始化hash元素的個數if(size < 0 || hash_value == NULL || keycmp == NULL) {return NULL;}h = (hashtab *)malloc(sizeof(hashtab));if(h == NULL) {return NULL;}h->htables = (hashtab_node**)malloc(size * sizeof(hashtab_node*));if(h->htables == NULL) {return NULL;}h->size = size;h->hash_value = hash_value;h->keycmp = keycmp;h->hash_node_free = hash_node_free;h->nel = 0;for(;hash_num < size; hash_num++) {h->htables[hash_num] = NULL;}return h;
}/* 銷毀 */
void hashtab_destory(hashtab *h)
{int i = 0;hashtab_node *cur = NULL;hashtab_node *tmp = NULL;if(h == NULL) {return;}for (;i < h->size; ++i) {cur = h->htables[i];while(cur != NULL) {tmp = cur;cur = cur->next;h->hash_node_free(tmp);}}free(h->htables);free(h);return ;
}/* 插入 */
int hashtab_insert(hashtab *h ,void *key, void *data) {if(h == NULL || key == NULL || data == NULL) {return -1;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hashtab_node *prev = NULL;hashtab_node *tmp = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];/* hashtable 中的元素在hash鏈上也是有序的 */while(cur != NULL && (h->keycmp(h,key,cur->key) > 0)) {// 找到待插入key的前驅節點prev = cur;cur = cur->next;}if(cur != NULL && (h->keycmp(h, key, cur->key) == 0)) { // 當前key存在return 1;}tmp = (hashtab_node *)malloc(sizeof(hashtab_node));if(tmp == NULL) {return -1;}tmp->key = key;tmp->data = data;if(prev == NULL) {tmp->next = h->htables[hvalue];h->htables[hvalue] = tmp;}else {tmp->next = prev->next;prev->next = tmp;}h->nel ++;// if(h->size * 3 / 4 <= h->nel) {// hashtab_expand(h);// }return 0;
}/* 刪除 */
hashtab_node *hashtab_delete(hashtab *h, void *key)
{if(h == NULL || key == NULL) {return NULL;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hashtab_node *prev = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];while(cur != NULL && (h->keycmp(h,key,cur->key) >= 0) ) {// 找到待刪除節點的前驅節點if(h->keycmp(h,key,cur->key) == 0) { // 找到匹配的keyif(prev == NULL) { // 需要刪除的key就是hvalue所在hash鏈上的第一個keyh->htables[hvalue] = cur -> next;}else {prev->next = cur->next;}h->nel --;return cur;}prev = cur;cur = cur ->next;}return NULL;
}/* 查找 */
void *hashtab_search(hashtab*h,void *key)
{if(h == NULL || key == NULL) {return NULL;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];if(cur == NULL) { // 先確認hash桶是否存在return NULL;}while(cur != NULL) {if(h->keycmp(h,key,cur->key) == 0) {return cur->data;}cur = cur ->next;}return NULL;
}void hashtab_dump(hashtab *h)
{int i = 0;hashtab_node *cur = NULL;if(h == NULL) {return;}printf("\r\n----開始--size[%d],nel[%d]------------", h->size, h->nel);for(;i< h->size; ++i) {printf("\r\n htables[%d]:",i);cur = h->htables[i];while(cur != NULL) {printf("key[%s],data[%s] ", cur->key, cur->data);cur = cur ->next;}}printf("\r\n----結束--size[%d],nel[%d]------------", h->size, h->nel);
}/* 擴容 */
void *hashtab_expand(hashtab *tmp_h) {if(tmp_h == NULL || (tmp_h ->size * 3 / 4 > tmp_h->nel)) {return NULL;}printf("begin expand\n");hashtab *new_h = NULL;hashtab *h = tmp_h;hashtab_node *cur = NULL;int i = 0;new_h = hashtab_create(h->size * 2, h->hash_value,h->keycmp, h->hash_node_free);for (;i < h->size; ++i) {cur = h->htables[i];while(cur != NULL) {hashtab_insert(new_h, cur->key, cur->data);cur = cur->next;}}printf("before destory\n");hashtab_destory(tmp_h);printf("end destory\n");// hashtab_dump(new_h);tmp_h = new_h;return NULL;
}struct test_node
{/* data */char key[30];char data[30];
};unsigned int simple_hash(const char *str)
{register unsigned int hash = 0;register unsigned int seed = 131;while(*str){hash = hash*seed + *str++;}return hash & (0x7FFFFFFF);
}int hashtable_hvalue(hashtab *h, const void *key)
{return simple_hash(key) % h->size;
}int hashtable_compare(hashtab*h, const void *key1, const void *key2)
{return strcmp(key1, key2);
}void hashtable_node_free(hashtab_node *cur)
{struct test_node *tmp = NULL;tmp = container(cur->key,struct test_node,key);free(tmp);free(cur);
}int main ()
{int res = 0;char *pres = NULL;hashtab_node * node = NULL;struct test_node *p = NULL;hashtab *h = NULL;h = hashtab_create(6,hashtable_hvalue,hashtable_compare,hashtable_node_free);// 創建一個hash桶大小為5的hash表assert(h!= NULL);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 輸入key value,輸入\"quit\"退出");scanf("%s",p->key);scanf("%s",p->data);if(strcmp(p->key,"quit") == 0){free(p);break;}res = hashtab_insert(h,p->key,p->data);if (res != 0){free(p);printf("\r\n key[%s],data[%s] insert failed %d",p->key,p->data,res);}else{printf("\r\n key[%s],data[%s] insert success %d",p->key,p->data,res);}}hashtab_dump(h);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 請輸入key 查詢value的數值,當可以等于\"quit\"時退出");scanf("%s",p->key);if(strcmp(p->key,"quit") == 0){free(p);break;}pres = hashtab_search(h,p->key);if (pres == NULL){printf("\r\n key[%s] search data failed",p->key);}else{printf("\r\n key[%s],search data[%s] success",p->key,pres);}free(p);}hashtab_dump(h);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 請輸入key 刪除節點的數值,當可以等于\"quit\"時退出");scanf("%s",p->key);if(strcmp(p->key,"quit") == 0){free(p);break;}node = hashtab_delete(h,p->key);if (node == NULL){printf("\r\n key[%s] delete node failed ",p->key);}else{printf("\r\n key[%s],delete data[%s] success",node->key,node->data);h->hash_node_free(node);}free(p);hashtab_dump(h);}hashtab_destory(h);return 0;}
總結
以上是生活随笔為你收集整理的数据结构 -- 散列表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 200斤啤酒酿酒设备多少钱一套
- 下一篇: 数据结构 -- 图与图存储