哈希表的目的简单来说是为了实现存储多个key=>value关系(注意,此处是单项推导,不支持反向查找),一个比较简单的模型实现是用一个数组来存储这些关系,但是在插入数据时,并不在index最小的数组位置插入,而是直接通过函数算出这个key-value应该存储的位置,这样可以避免查找时遍历查找。
一个比较简单的实现方法是这样:
typedef struct
{
T_Val value;
T_Pos next;
}T_Item;
struct _HashTable
{
T_Item items[];
T_Pos hash_list_Headers[];
}T_HashTable;
将具有相同hash值的条目组成一个链表,用T_Item::next域表示链表的下一个的位置,当插入的时候,首先计算出hash值,以该值作为索引,通过T_HashTable::hash_list_headers,找到该哈希链表的地一个位置T_Pos,然后遍历该链表,找到需要的key对应的value。
这种方法有什么缺点呢?
1. 链表在物理内存中不连续,造成cache命中减小
2. 如果用单项链表,则删除节点十分麻烦
3. 效率很低
一群牛人们在glib库中重新写了hash算法的模板,本文在此分析一下他的运行过程
大体算法:
gpointer *keys;
guint *hashes;
gpointer *values;
这三个数组长度相同,下标为x的三个元素 key[x] hashes[x] values[x],共同描述这同一个key-value关系。
不会出现当x,y,z任意两个不想等时,key[x] hashes[y] values[z],共同描述一个key-value关系。当插入一个
key-value关系时,伪代码如下:
find(key)
{
hash_val = get_hash_by_key( key);
for ( i = hash_val; HASH_IS_USED( hashes[i] ); i++ )
{
if ( HASH_VAL_TOMB == hash[i] )
continue;
if ( hash[i] == hash_val && keys[i] == key)
return values[i];
}
}
首先计算该key的hash值,以该哈希值x为起始数组位置,找到hashes[x]与keys[x]同时匹配时,返回value
先来看一下最重要的数据结构,这个_GHashTable数据结构写在c文件中,对外不暴露细节,在glib.h文件中被
typedef为 GHashTable
struct GHashTable
{
gint size;
gint mod;
guint mask;
gint nnodes;
gint noccupied; /* nnodes + tombstones */
gpointer *keys;
guint *hashes;
gpointer *values;
GHashFunc hash_func;
GEqualFunc key_equal_func;
gint ref_count;
#ifndef G_DISABLE_ASSERT
/*
* Tracks the structure of the hash table, not its contents: is only
* incremented when a node is added or removed (is not incremented
* when the key or data of a node is modified).
*/
int version;
#endif
GDestroyNotify key_destroy_func;
GDestroyNotify value_destroy_func;
};
GHashTable *
g_hash_table_new (GHashFunc hash_func,
GEqualFunc key_equal_func)
{
return g_hash_table_new_full (hash_func, key_equal_func, NULL, NULL);
}
GHashTable *
g_hash_table_new_full (GHashFunc hash_func,
GEqualFunc key_equal_func,
GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func)
{
GHashTable *hash_table;
hash_table = g_slice_new (GHashTable);
/*g_slice_new和new是一样的*/
g_hash_table_set_shift (hash_table, HASH_TABLE_MIN_SHIFT);
/*这个函数很有意思,下文着重分析*/
hash_table->nnodes = 0;
/*这个哈希表中,实际被真正占用的有多少个*/
hash_table->noccupied = 0;
/*noccupied = nnodes + tombs
tomb 坟墓,也就是被删除了的元素!
*/
hash_table->hash_func = hash_func ? hash_func : g_direct_hash;
hash_table->key_equal_func = key_equal_func;
hash_table->ref_count = 1;
#ifndef G_DISABLE_ASSERT
hash_table->version = 0;
#endif
hash_table->key_destroy_func = key_destroy_func;
hash_table->value_destroy_func = value_destroy_func;
hash_table->keys = g_new0 (gpointer, hash_table->size);
hash_table->values = hash_table->keys;
/*初始化时,将keys和values指向同一段内存,这是为了防止有key与value一直相等的情况,这样做可以节省内存*/
hash_table->hashes = g_new0 (guint, hash_table->size);
/*g_new0就是new*/
return hash_table;
}
插入过程
gboolean
g_hash_table_insert (GHashTable *hash_table,
gpointer key,
gpointer value)
{
return g_hash_table_insert_internal (hash_table, key, value, FALSE);
}
static gboolean
g_hash_table_insert_internal (GHashTable *hash_table,
gpointer key,
gpointer value,
gboolean keep_new_key)
{
guint key_hash;
guint node_index;
g_return_val_if_fail (hash_table != NULL, FALSE);
/*hash_table==NULL 直接退出*/
node_index = g_hash_table_lookup_node (hash_table, key, &key_hash);
/*这个函数要么找到命中的node_index,如果没找到,则返回一个坟墓或者空项目的指针 */
return g_hash_table_insert_node (hash_table, node_index, key_hash, key, value, keep_new_key, FALSE);
}
static inline guint
g_hash_table_lookup_node (GHashTable *hash_table,
gconstpointer key,
guint *hash_return)
{
guint node_index;
guint node_hash;
guint hash_value;
guint first_tombstone = 0;
gboolean have_tombstone = FALSE;
guint step = 0;
g_assert (hash_table->ref_count > 0);
/*这里计算哈希值之后,又对哈希值做了处理,使之不能大于等于2*/
/*原因在于 0 表示该hash数组元素没有使用,1表示是坟墓*/
hash_value = hash_table->hash_func (key);
if (G_UNLIKELY (!HASH_IS_REAL (hash_value)))
hash_value = 2;
*hash_return = hash_value;
node_index = hash_value % hash_table->mod;
node_hash = hash_table->hashes[node_index];
/*tomb的hash为1 算是已使用,
正常的hash >= 2
未使用hash = 0*/
/*这里不会产生死循环,因为在要满之前,就会扩充内存,所以总会保证能够有一个空的哈希元素的
g_hash_table_resize*/
while (!HASH_IS_UNUSED (node_hash))
{
if (node_hash == hash_value)
{
/*哈希命中,则比对key*/
gpointer node_key = hash_table->keys[node_index];
if (hash_table->key_equal_func)
{
if (hash_table->key_equal_func (node_key, key))
return node_index;
}
else if (node_key == key)
{
return node_index;
}
}
else if (HASH_IS_TOMBSTONE (node_hash) && !have_tombstone)
{
/*如果是一个坟墓,则把下标记录下来,这样这个下标就可以作为插入的时候参考用*/
first_tombstone = node_index;
have_tombstone = TRUE;
}
/*只要不return 或者遇到没有使用的hash,则一直循环*/
step++;
node_index += step;
node_index &= hash_table->mask;
node_hash = hash_table->hashes[node_index];
}
/*走到这里发现,一直持续循环最后还是没命中,node_index*/
if (have_tombstone)
return first_tombstone;
return node_index;
}
g_hash_table_lookup (GHashTable *hash_table,
gconstpointer key)
{
guint node_index;
guint node_hash;
g_return_val_if_fail (hash_table != NULL, NULL);
/*这里查询key又调用了这个函数*/
node_index = g_hash_table_lookup_node (hash_table, key, &node_hash);
return HASH_IS_REAL (hash_table->hashes[node_index])
? hash_table->values[node_index]
: NULL;
}