C中的通用哈希表
Posted
技术标签:
【中文标题】C中的通用哈希表【英文标题】:Generic hashtable in C 【发布时间】:2017-05-06 09:59:20 【问题描述】:我正在尝试在 C 中创建一个通用哈希表。我已经阅读了一些不同的实现,并遇到了几种不同的方法。
首先是使用这样的宏:http://attractivechaos.awardspace.com/khash.h.html
第二个是使用带有 2 个 void 指针的结构,如下所示:
struct hashmap_entry
void *key;
void *value;
;
据我所知,这种方法并不好,因为这意味着映射中的每个条目至少需要 2 次分配:一个用于键,一个用于值,无论存储的数据类型如何。 (是吗???)
在不走宏观路线的情况下,我无法找到一种保持通用性的体面方法。有没有人有任何可以帮助我的提示或示例?
【问题讨论】:
宏是一种重复代码的机制。它们不是数据结构。不依赖某种动态内存分配,您不太可能实现通用哈希表。 此外,包含指针的数据结构与动态分配无关。 @JohnBollinger 你的第一点是有道理的。如果您查看我发布的链接,您就会明白我的意思。回复:指针...我的意思是,由于它包含指针,如果您尝试在没有动态分配的情况下将项目添加到地图中,那么如果您尝试在这些项目所在的函数之外使用该地图,那么它将是无效的,对吗?跨度> 嗯,这是可能的,但并非没有限制和很多技巧。 总的来说,我的观点是,您以两种不同的方法呈现的内容是不合适的。很有可能,您的示例基于指针的实现使用包含指针的数据结构,类似于您作为替代方案提供的那种。在这种情况下,您只是不直接操纵它。您甚至可能会发现宏执行动态内存管理。你在比较苹果和建筑。 【参考方案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 中,您可以验证请求的类型是否正确。
否则,要使所有内容都通用,您只能使用宏或更改语言。
【讨论】:
我不确定我是否关注你。您的示例是否应该呈现结构的成员?如果是这样,它们就不起作用。 这就是为什么我没有把它们放在struct
中的原因:) 你不能这样定义结构,因为在编译时字段的长度是未知的。 (因为 C 没有泛型类型!)您可以通过自己分配正确的大小并手动填写字段来伪造它。您还可以通过仅将长度设置为结构并将分配内存的开头转换为该结构来帮助自己。
好吧,C 结构可以有灵活的数组成员(每个最多一个),所以你的断言不太正确。但无论如何,现在我确定我没有遵循你的实际建议,或者它如何比他提出的替代方案更好地服务于 OP 的目标。【参考方案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);
这种方法不会提供类型安全,因为假设每个应用程序结构都以 HashItemCore
成员开头,项目将作为 void 指针传递。这将是一种手工制作的多态性。这可能并不完美,但这会奏效。
我使用模板在 C++ 中实现了这种方法。但是,如果你去掉所有对 C++ 的幻想,简而言之,这正是我上面所描述的。我在多个项目中使用了我的桌子,它很有魅力。
【讨论】:
【参考方案3】:C 中的通用哈希表是个坏主意。
整洁实现需要函数指针,这很慢,因为这些函数不能内联(一般情况下,每跳至少需要两个函数调用:一个计算哈希值和一个用于最终比较) 要允许内联函数,您要么必须 手动编写代码 或使用代码生成器 或宏。这可能会变得混乱IIRC,Linux 内核使用宏来创建和维护(一些?)它的哈希表。
【讨论】:
以上是关于C中的通用哈希表的主要内容,如果未能解决你的问题,请参考以下文章