【发布时间】:2014-10-30 09:01:53
【问题描述】:
我在一个库中工作,其中一些在这里无关紧要的元素必须用名称标识(即值与名称相关联)。名称是用户的字符串,无论其内部表示是什么,并且应该表现得透明。
- 名称是常量并使用字符串字面量进行初始化。它们在编译时是已知的。
- 使用不同字符串初始化的两个名称必须比较不同,无论它们的内部表示是什么。
- 名称可以是任意长度。我的图书馆没有设置任何限制。
- 实际上,可能的名称不能有限制。实现的约束不能影响接口,因此,我这边也没有限制。
考虑到会频繁查找,我考虑过使用无序映射。
无序关联容器通过数字(通常为std::size_t 类型)存储其元素,无论其类型是什么,这些都是通过散列函数获得的。这意味着:
- 对于可能值的数量小于或等于哈希值数量的类型,不应发生冲突。
- 对于可能值的数量大于散列值的类型,可能会发生冲突,因为在散列过程中会丢失一些数据。
我想到了两种解决方案。
按值散列
使用数据本身来计算哈希值。注意事项:
-
可能在编译时计算。 由于名称是从字符串文字构造的,因此构造函数(即
constexpr本身)可以调用constexpr散列函数,并将散列值存储在类本身,以便稍后(通过哈希对象)快速检索。 - 冲突多久会发生一次?哪种算法最好?
按顺序散列
正如here 所解释的,Boost.Log 库维护一个全局(即静态)表,该表将名称与其哈希值相关联。可能的实现如下:
- 当(从字符串文字)构造名称时,会查找表(执行精确比较)。
- 如果未找到,则在容器末尾注册。
- 其条目在表中的偏移量成为其哈希值。
注意事项:
-
非常慢。 对于每个构造的名称,必须执行与注册名称一样多的字符串比较。这并不比传统的
std::map好多少,是吗? - 线程不安全。该表必须受到保护。
- 在运行时强制完成。
问题
-
在这些条件下使用无序映射是否正确? 改用
std::map会更好吗? - 如果 1 是“是”,哪种方法更好,为什么? Boost.Log 中使用的一种似乎效率很低,为什么使用它而不是我解释的另一种,即使字符串在编译时不一定知道?
注意:尽管我可以访问 gcc 和 clang 提供的实验性支持,但我还没有添加 c++14 标签。请不要犹豫,使用即将发布的规范中包含的功能。
【问题讨论】:
-
我刚刚注意到哈希冲突只会损害性能,而不是功能(键的唯一性):多个对可能共存于同一个桶中。考虑到这一点,
std::unordered_map以及constexpr散列函数似乎是解决方案。
标签: c++ c++11 hash hashmap hashtable