glib GHashTable 哈希表代码研究

一、哈希表介绍
哈 希表与线性表的区别在于查找效率。线性表查找的时间复杂度通常为O(n),二分查找则达到了O(logn),哈希表可达到O(1),速度大为提高。这是一 种以空间换时间的方法。那么哈希表是怎么做到的呢?对于线性表的查找通常是使用遍历的方法,使用二分查找构造时要麻烦一些。哈希表则使用了哈希值(或哈希 值求模)来作为数组下标来直接定位,从而实现了高效率。

二、glib哈希表的实现
glib的哈希表的所有实现代码 在源码目录下glib/ghash.c文件中,它实现了对任意类型key和任意类型value的存取。这里参考了glib-2.16的代码。

1、 数据结构
struct _GHashNode
{
  gpointer   key;           // key值,可以是任意类型
  gpointer   value;        // key对应的值
  GHashNode *next;    // 指向下个节点
  guint      key_hash;   // hash_func(key)得到的uint值
};

struct _GHashTable
{
  gint             size;                   // 哈希表大小
  gint             nnodes;              // 哈希表节点数量,即总共有多少对key/value
  GHashNode      **nodes;        // 节点链表指针数组
  GHashFunc        hash_func;   // 哈希函数,由使用者提供
  GEqualFunc       key_equal_func;    // 当key_hash相同时用这个判断key是否相等
  volatile 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;        // 释放key的函数
  GDestroyNotify   value_destroy_func;      // 释放value的函数
};

2、新 建
hash_func和key_equal_func由调用者提供,也可以为空。如果hash_func为空会使用g_direct_hash方 法来替代,实际上是用指针作为key值;如果key_equal_func为空则使用“==”来代替,实质上是指针比较,如整型值保存为指针就可不提供 key_equal_func方法。
key_destroy_func和value_destroy_func通常是需要 的,g_hash_table_destroy或g_hash_table_remove时会调用这两个函数。当然,如果哈希表中的key和value指 向的值不需要释放或在其他地方管理就不需要了。
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);
  hash_table->size               = HASH_TABLE_MIN_SIZE;    // MIN_SIZE为11   
  hash_table->nnodes             = 0;
  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->nodes              = g_new0 (GHashNode*, hash_table->size);

  return hash_table;
}

3、插入
哈希 表是不允许key值有重复的,如果遇到重复的情况会根据keep_new_key来决定保存原值还是新值。在g_hash_table_insert() 里keep_new_key为TRUE值,即销毁老数据,g_hash_table_replace()里则keep_new_key为FALSE。
static void
g_hash_table_insert_internal (GHashTable *hash_table,
                              gpointer    key,
                              gpointer    value,
                              gboolean    keep_new_key)
{
  GHashNode **node_ptr, *node;
  guint key_hash;

  g_return_if_fail (hash_table != NULL);
  g_return_if_fail (hash_table->ref_count > 0);

  node_ptr = g_hash_table_lookup_node (hash_table, key, &key_hash);

  if ((node = *node_ptr))
    {
      if (keep_new_key)
        {
          if (hash_table->key_destroy_func)
            hash_table->key_destroy_func (node->key);
          node->key = key;
        }
      else
        {
          if (hash_table->key_destroy_func)
            hash_table->key_destroy_func (key);
        }

      if (hash_table->value_destroy_func)
        hash_table->value_destroy_func (node->value);

      node->value = value;
    }
  else
    {
      node = g_slice_new (GHashNode);

      node->key = key;
      node->value = value;
      node->key_hash = key_hash;
      node->next = NULL;

      *node_ptr = node;
      hash_table->nnodes++;
      g_hash_table_maybe_resize (hash_table);

#ifndef G_DISABLE_ASSERT
      hash_table->version++;
#endif
    }
}

4、删除
先查找key值 对应的节点,如果未找到则直接返回,找到则删除该节点。
static gboolean
g_hash_table_remove_internal (GHashTable    *hash_table,
                              gconstpointer  key,            
                              gboolean       notify)         
{
  GHashNode **node_ptr;

  g_return_val_if_fail (hash_table != NULL, FALSE);

  node_ptr = g_hash_table_lookup_node (hash_table, key, NULL);
  if (*node_ptr == NULL)
    return FALSE;

  g_hash_table_remove_node (hash_table, &node_ptr, notify);
  g_hash_table_maybe_resize (hash_table);

#ifndef G_DISABLE_ASSERT      
  hash_table->version++;      
#endif

  return TRUE;                
}

5、查找
先用hash_func(key)得到key的哈希值,用这个哈 希值对size求模来找到对应哈希值的节点链表头。然后再用key_equal_func比较key后找到节点位置。
static inline GHashNode **
g_hash_table_lookup_node (GHashTable    *hash_table,
                          gconstpointer  key,
                          guint         *hash_return)
{
  GHashNode **node_ptr, *node;
  guint hash_value;

  hash_value = (* hash_table->hash_func) (key);
  node_ptr = &hash_table->nodes[hash_value % hash_table->size];

  if (hash_return)
    *hash_return = hash_value;

  /* Hash table lookup needs to be fast.
   *  We therefore remove the extra conditional of testing
   *  whether to call the key_equal_func or not from
   *  the inner loop.
   *
   *  Additional optimisation: first check if our full hash
   *  values are equal so we can avoid calling the full-blown
   *  key equality function in most cases.
   */
  if (hash_table->key_equal_func)
    {
      while ((node = *node_ptr))
        {
          if (node->key_hash == hash_value &&
              hash_table->key_equal_func (node->key, key))
            break;

          node_ptr = &(*node_ptr)->next;
        }
    }
  else
    {
      while ((node = *node_ptr))
        {
          if (node->key == key)
            break;

          node_ptr = &(*node_ptr)->next;
        }
    }

  return node_ptr;
}

6、调整大小
GHashTable结构中的size是哈希表的大小,nnodes是节点数量(即哈 希表中有多少对值)。如果size大于等于3倍nnodes或nnodes大于3倍size,哈希表就会重新构造。重构会带来不小的开销,所有节点的 key_hash都要重新生成,因此选择一个好的hash_func很重要。
static inline void
g_hash_table_maybe_resize (GHashTable *hash_table)
{
  gint nnodes = hash_table->nnodes;
  gint size = hash_table->size;

  if ((size >= 3 * nnodes && size > HASH_TABLE_MIN_SIZE) ||
      (3 * size <= nnodes && size < HASH_TABLE_MAX_SIZE))
    g_hash_table_resize (hash_table);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: glib2.0是常用的一个开源库。它提供了许多C语言编程接口,可以用于处理各种数据结构、文件I/O、字符串操作、进程控制等任务。下面提供一个简单的示例代码,演示如何使用glib2.0来读取一个文件的内容并计算出其中字母'A'出现的次数。 ```c #include <stdio.h> #include <stdlib.h> #include <glib.h> int main(int argc, char **argv) { GError *error = NULL; GIOChannel *channel; gchar *line = NULL; gsize length = 0; guint count = 0; if (argc != 2) { printf("Usage: %s filename\n", argv[0]); exit(1); } channel = g_io_channel_new_file(argv[1], "r", &error); if (channel == NULL) { fprintf(stderr, "g_io_channel_new_file error: %s\n", error->message); g_error_free(error); exit(1); } while (g_io_channel_read_line(channel, &line, &length, NULL, &error) == G_IO_STATUS_NORMAL) { gchar *p = line; while (*p) { if (*p == 'A') { count++; } p++; } g_free(line); } if (error != NULL) { fprintf(stderr, "g_io_channel_read_line error: %s\n", error->message); g_error_free(error); exit(1); } printf("Number of 'A': %u\n", count); g_io_channel_unref(channel); return 0; } ``` 上述代码首先判断传入的参数是否正确,申请内存用于处理文件,然后循环读取文件中的每一行,并在每一行中查找字母'A',最后输出'A'出现的次数。 总的来说,glib2.0库提供了非常丰富的函数和数据类型,可以很方便地完成各种任务。在实际使用中,可以参考官方文档和示例代码,进行深入学习和应用。 ### 回答2: glib2.0 是 Linux 平台上的一个基础库,提供了大量的功能,包括字符串、文件、内存操作、数据结构、线程等,让程序开发更加简单和高效。下面是一个简单的 glib2.0 示例代码: #include <stdio.h> #include <glib.h> int main(int argc, char *argv[]) { GString *str = g_string_new("Hello, "); g_string_append(str, "glib2.0"); printf("%s\n", str->str); g_string_free(str, TRUE); return 0; } 这个程序首先创建了一个 GString 对象,然后使用 g_string_append() 函数将字符串追加到 GString 中,最后打印出 GString 的内容并且释放 GString 占用的内存空间。 GString 是 glib2.0 中的一个字符串类型,功能类似于 C 语言中的 char 数组,但是 GString 可以动态地扩展内存空间,避免了计算空间大小的麻烦和内存溢出的问题。在这个程序中,使用 g_string_new() 函数创建一个新的 GString 对象,然后使用 g_string_append() 函数将字符串追加到 GString 中。最后,使用 g_string_free() 函数释放 GString 对象占用的内存空间。 以上是一个简单的 glib2.0 示例代码,使用 glib2.0 可以提高程序开发效率,让程序变得更加高效稳定。 ### 回答3: glib2.0 是一个非常常用的 C 语言开发库,它提供了许多通用的数据结构和函数,可以帮助我们写出高效的程序。下面是一个 glib2.0 的示例代码: ``` #include <glib.h> int main(int argc, char **argv) { // 创建一个 GList 对象来存储一些字符串 GList *list = NULL; list = g_list_append(list, "apple"); list = g_list_append(list, "banana"); list = g_list_append(list, "cherry"); // 遍历列表并打印每个字符串 GList *iter; for (iter = list; iter != NULL; iter = iter->next) { printf("%s\n", (char *)iter->data); } // 释放 GList 对象的内存 g_list_free(list); return 0; } ``` 这个示例代码演示了如何使用 glib2.0 创建一个 `GList` 对象来存储字符串,并遍历该列表来打印每个字符串。在程序末尾,通过调用 `g_list_free()` 函数来释放 `GList` 对象的内存。除了 `GList`,glib2.0 还提供了许多其他的数据结构和函数,例如哈希表、字符串处理等等,可以帮助我们更轻松地编写程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值