C言語のHashTableを簡単に実現
HashTableは実際の応用において重要な構造であり,以下では簡単な実装について議論するが,その一部はまだある.
一、アクセスインタフェース
hashtableを作成します.
hashtable hashtable_新(int size)/sizeは含む接点個数を表す.
key-valueをhashtableに格納します.
void hashtable_put(hashtable h,const char* key,void *val);
キーに基づいてhashtableからvalue値を取り出します.
void * hashtable_get(hashtable h,const char *key);
hashtableを解放します.
void hashtable_free(hashtable h);
単一hash接点の解放
void hashtable_delete_node(hashtable h, const char *key);
二、データ構造
hash接点の構造:
この構造は、必要なkey-valueに加えて、競合するためのチェーンテーブル構造を含むことが容易に理解できます.
hashtableのデータ構造:
pool_t:メモリプール構造はhashtableが使用するメモリを管理する.構造参照「C言語メモリプール使用モデル」
size:現在のhashの接点空間サイズ.
count:現在の接点空間で使用可能なhash接点の数を表します.
z:接点空間に接点を格納するために使用します.
三、hashtableの作成
コードは次のとおりです.
四、key-value値を格納
この操作の前に、KEY値に基づいてhashcodeを計算する関数を定義します.
コードは次のとおりです.
五、HASHTABLEから接点を取得する
KEYに従ってhashtableから接点を取得するには、まずKEYに基づいてhash値を計算し、hashtableから指定した接点または接点チェーンテーブルを見つける.次のようになります.
六、HASHTABLEをリリース
hashtableの解放は簡単です.私たちのすべてのメモリ申請はメモリプールで完了しているので、メモリプールを解放するだけです.以下のようにします.
七、単一hash接点を解放する
コードは次のとおりです.
これは単純なHASHTABLE構造を実現しているが、もちろん後には不足している.例えばHASHTABLEを遍歴し、配列で遍歴すれば効率が低いに違いない.以下、hashtableを遍歴するための実現案を議論する.
八,hashtableの遍歴討論
直接配列を使うとhashtableのstruct hashnode_struct配列は遍歴可能ですが、1つの接点だけが含まれている場合は、次のようにすべての配列を遍歴します.
このように効率が低く,実際には接点にnextドメインが含まれており,これを用いて遍歴を実現できる.
前のhashtableデータ構造を簡単に変更し、2つのドメインを追加する必要があります. nodeは現在の遍歴カーソルを表し、遍歴中にこの接点が指す接点を絶えず移動する. bucketは、現在のnodeがどのバケツにあるかを記録するためにnodeに関連付けられている.
まず接続を確立することは、すべての接点を接続することであり、慣例に従って、XXX_を採用することである.iter_first関数は、以下のように初期化されます.
削除された接点がnodeの場合、次の処理が必要です.
一、アクセスインタフェース
hashtableを作成します.
hashtable hashtable_新(int size)/sizeは含む接点個数を表す.
key-valueをhashtableに格納します.
void hashtable_put(hashtable h,const char* key,void *val);
キーに基づいてhashtableからvalue値を取り出します.
void * hashtable_get(hashtable h,const char *key);
hashtableを解放します.
void hashtable_free(hashtable h);
単一hash接点の解放
void hashtable_delete_node(hashtable h, const char *key);
二、データ構造
hash接点の構造:
typedef struct hashnode_struct{
struct hashnode_struct *next;
const char *key;
void *val;
}*hashnode,_hashnode;
この構造は、必要なkey-valueに加えて、競合するためのチェーンテーブル構造を含むことが容易に理解できます.
hashtableのデータ構造:
typedef struct hashtable_struct{
pool_t p;
int size;
int count;
struct hashnode_struct *z;
}*hashtable,_hashtable;
この構成について以下に説明する.pool_t:メモリプール構造はhashtableが使用するメモリを管理する.構造参照「C言語メモリプール使用モデル」
size:現在のhashの接点空間サイズ.
count:現在の接点空間で使用可能なhash接点の数を表します.
z:接点空間に接点を格納するために使用します.
三、hashtableの作成
コードは次のとおりです.
hashtable hashtable_new(int size)
{
hashtable ht;
pool_t p;
p = _pool_new_heap(sizeof(_hashnode)*size + sizeof(_hashtable));
ht= pool_malloc(p, sizeof(_hashtable));
ht->size = size;
ht->p = p;
ht->z = pool_malloc(p, sizeof(_hashnode)*prime);
return ht;
}
この関数は比較的簡単で、まずメモリプールを定義して初期化し、サイズはsizeによって決まるので、実際に使用するときは、私たちのsizeは相対的に大きい点を割り当てるべきで、比較的良いです.四、key-value値を格納
この操作の前に、KEY値に基づいてhashcodeを計算する関数を定義します.
static int hashcode(const char *s, int len)
{
const unsigned char *name = (const unsigned char *)s;
unsigned long h = 0, g;
int i;
for(i=0;i<len;i++)
{
h = (h << 4) + (unsigned long)(name[i]); //hash 4 , ASCII hash
if ((g = (h & 0xF0000000UL))!=0)
h ^= (g >> 24);
h &= ~g; // 28-31 。
}
return (int)h;
}
この関数は、エレガントなELF hash関数を使用します.コードは次のとおりです.
void hashtable_put(hashtable h, const char *key, void *val)
{
if(h == NULL || key == NULL)
return;
int len = strlen(key);
int index = hashcode(key,len);
hashtable node;
h->dirty++;
if((node = hashtable_node_get(h, key,len, index)) != NULL) // , , 。
{
n->key = key;
n->val = val;
return;
}
node = hashnode_node_new(h, index); // HASH NODE 。
node->key = key;
node->val = val;
}
hashtable_node_getは、KEYがHASHに既に存在するかどうかを検索するために使用され、以下のように簡単に実現される.static hashnode hashtable_node_get(hashtable h, const char *key, int len, int index)
{
hashnode node;
int i = index % h->size;
for(node = &h->z[i]; node != NULL; node = node->next) // index [HASH ] HASH
if(node->key != NULL && (strlen(node->key)==len) && (strncmp(key, node->key, len) == 0))
return node;
return NULL;
}
新しいHASH NODE接点は以下の通りである:static hashnode hashnode_node_new(hashtable h, int index)
{
hashnode node;
int i = index % h->size;
h->count++;
for(node = &h->z[i]; node != NULL; node = node->next)
if(node->key == NULL) // : HASH ,KEY , , 。
return node;
node = pool_malloc(h->p, sizeof(_hashnode)); //
node->next = h->z[i].next; // , 。
h->z[i].next = node;
return node;
}
五、HASHTABLEから接点を取得する
KEYに従ってhashtableから接点を取得するには、まずKEYに基づいてhash値を計算し、hashtableから指定した接点または接点チェーンテーブルを見つける.次のようになります.
void *hashtable_get(hashtable h, const char *key)
{
if(h == NULL || key == NULL)
return NULL;
hashnode node;
int len = strlen(key);
if(h == NULL || key == NULL || len <= 0 || (node = hashtable_node_get(h, key, len, hashcode(key,len))) == NULL)
{
return NULL;
}
return node->val;
}
という関数は簡単に理解できます.六、HASHTABLEをリリース
hashtableの解放は簡単です.私たちのすべてのメモリ申請はメモリプールで完了しているので、メモリプールを解放するだけです.以下のようにします.
void hashtable_free(hashtable h)
{
if(h != NULL)
pool_free(h->p);
}
七、単一hash接点を解放する
コードは次のとおりです.
void hashtable_delete_node(hashtable h, const char *key)
{
if(h == NULL || key == NULL)
return;
hashnode node;
int len = strlen(key);
if(h == NULL || key == NULL || (node = hashtable_node_get(h, key, len, hashcode(key,len))) == NULL) //
return;
node->key = NULL;
node->val = NULL;
h->count--;
}
これは単純なHASHTABLE構造を実現しているが、もちろん後には不足している.例えばHASHTABLEを遍歴し、配列で遍歴すれば効率が低いに違いない.以下、hashtableを遍歴するための実現案を議論する.
八,hashtableの遍歴討論
直接配列を使うとhashtableのstruct hashnode_struct配列は遍歴可能ですが、1つの接点だけが含まれている場合は、次のようにすべての配列を遍歴します.
void hashtable_traverse(hashtable h)
{
int i;
hashnode node;
if(h == NULL)
return;
for(i = 0; i < h->prime; i++)
for(node = &h->z[i]; node != NULL; node = node->next)
if(node->key != NULL && node->val != NULL)
XXXXXXXXXXXXXXXXX // 。
}
このように効率が低く,実際には接点にnextドメインが含まれており,これを用いて遍歴を実現できる.
前のhashtableデータ構造を簡単に変更し、2つのドメインを追加する必要があります.
typedef struct hashtable_struct{
pool_t p;
int size;
int count;
struct hashnode_struct *z;
int bucket;
hashnode node;
}*hashtable,_hashtable;
はbucketとnodeの2つのドメインを追加し、この2つのドメインを追加する考え方です.まず接続を確立することは、すべての接点を接続することであり、慣例に従って、XXX_を採用することである.iter_first関数は、以下のように初期化されます.
int hashtable_iter_first(hashtable h) {
if(h == NULL)
return 0;
h->bucket = -1;
h->node = NULL;
return hashtable_iter_next(h);
}
hashtable_iter_nextは次の接点を取得するために使用され、カーソルが確定した場合、次の接点はすぐに確定され、以下のように定義されます.int xhash_iter_next(xht h) {
if(h == NULL) return 0;
while(h->node != NULL) {
h->node = h->node->next; // , ,
if(h->node != NULL && h->node->key != NULL && h->node->val != NULL)
return 1;
}
for(h->bucket++; h->bucket < h->prime; h->bucket++) {
h->node = &h->z[h->bucket];
while(h->node != NULL) {
if(h->node->key != NULL && h->node->val != NULL)
return 1;
h->node = h->node->next;
}
}
h->bucket = -1; // 。
h->node = NULL;
return 0;
}
上記の2つの方法の後、遍歴操作は以下の通りである.hashtable ht
if(hashtable_iter_first(ht)) // 。
do{
// ht->node, 。
}while(hashtable_iter_next(ht)); //
このように処理すると、より効率的になるのではないでしょうか.もちろん1回目では、配列全体と配列の下のバケツの接点を巡る必要があります.しかし、このような操作の後、ノードを削除するときは、いくつかの操作が必要です.1つの接点を削除する場合は、現在のh->nodeが現在削除されている接点であるかどうかを考慮し、もしそうであれば、h->nodeを次の接点と呼ぶ.削除した後、削除した場合は、次のように処理します.削除された接点がnodeの場合、次の処理が必要です.
if(h->node == n)
hashtable_iter_next(h);
h->nodeを次の接点に移動します.