C

时间:2016-12-21 04:12:39

标签: c hashmap hashtable

我试图在C中创建一个通用哈希表。我已经阅读了几个不同的实现,并遇到了几种不同的方法。

第一种是使用这样的宏:http://attractivechaos.awardspace.com/khash.h.html

第二个是使用带有2个void指针的结构,如下所示:

struct hashmap_entry
{
    void *key;
    void *value;
};

据我所知,这种方法并不好,因为这意味着地图中的每个条目至少需要2个分配:一个用于密钥,一个用于值,而不管存储的数据类型如何。 (是吗???)

我没有能够找到一种不用宏观路线保持通用性的好方法。有没有人有任何可能帮助我的提示或例子?

3 个答案:

答案 0 :(得分:2)

C不能直接提供你需要的东西,但是你可能想做这样的事情:

想象一下,您的哈希表是双链表的固定大小数组,并且可以在应用程序层上始终分配/销毁项。这些条件不适用于所有情况,但在许多情况下它们会。然后,您将拥有这些数据结构以及函数和原型的草图:

struct HashItemCore
{
    HashItemCore  *m_prev;
    HashItemCore  *m_next;
};

struct HashTable
{
    HashItemCore  m_data[256];    // This is actually array of circled
                                  // double linked lists.
    int     (*GetHashValue)(HashItemCore *item);
    bool    (*CompareItems)(HashItemCore *item1, HashItemCore *item2);
    void    (*ReleaseItem)(HashItemCore *item);
};

void  InitHash(HashTable *table)
{
    // Ensure that user provided the callbacks.
    assert(table->GetHashValue != NULL && table->CompareItems != NULL && table->ReleaseItem != NULL);

    // Init all double linked lists. Pointers of empty list should point to themselves.
    for (int i=0; i<256; ++i)
       table->m_data.m_prev = table->m_data.m_next = table->m_data+i;
}

void  AddToHash(HashTable *table, void *item);
void *GetFromHash(HashTable *table, void *item);
....
void *ClearHash(HashTable *table);

在这些函数中,您需要实现哈希表的逻辑。在工作时,他们将调用用户定义的回调来找出插槽的索引以及项目是否相同。

此表的用户应为他们想要使用的每对类型定义自己的结构和回调函数:

struct HashItemK1V1
{
     HashItemCore m_core;
     K1 key;
     V1 value;
};

int CalcHashK1V1(void *p)
{
     HashItemK1V1 *param  = (HashItemK1V1*)p;
     // App code.
}

bool CompareK1V1(void *p1, void *p2)
{
     HashItemK1V1 *param1  = (HashItemK1V1*)p1;
     HashItemK1V1 *param2  = (HashItemK1V1*)p2;
     // App code.
}

void FreeK1V1(void *p)
{
     HashItemK1V1 *param  = (HashItemK1V1*)p;
     // App code if needed.
     free(p);
}

这种方法不会提供类型安全性,因为项目将作为void指针传递,假设每个应用程序结构都以HashItemCore成员开头。这将是一种手工制作的多态性。这可能并不完美,但这可行。

我使用模板在C ++中实现了这种方法。但是,如果你要删除C ++的所有幻想,简而言之,这正是我上面所描述的。我在多个项目中使用了我的表,它就像魅力一样。

答案 1 :(得分:1)

C没有通用数据类型,所以你想要做的事情(没有额外的分配和没有void*投射)是不可能的。您可以使用宏来动态生成正确的数据函数/结构,但您也试图避免使用宏。

所以你需要放弃至少一个想法。

通过分配类似:

之类的东西,你可以拥有一个没有额外分配的通用数据结构
size_t key_len;
size_t val_len;
char key[];
char val[];

一次性然后分发void指针,或为每种特定类型添加api。

或者,如果您需要处理的类型数量有限,您也可以使用正确的值标记值,因此现在每个条目都包含:

size_t key_len;
size_t val_len;
int val_type;
char key[];
char val[];

但至少在API中你可以验证所请求的类型是否正确。

否则,为了使所有内容都通用,您可以使用宏或更改语言。

答案 2 :(得分:1)

C中的通用哈希表是个坏主意。

  • 一个 neat 实现将需要函数指针,这些指针很慢,因为这些函数无法内联(一般情况下每个跃点至少需要两个函数调用 :一个计算哈希值,最后一个比较计算一个)
  • 允许内联您需要的功能
    • 手动编写代码
    • 或使用代码生成器
    • 或宏。哪个会搞乱?
IIRC,Linux内核使用宏来创建和维护(某些?)其哈希表。