唯一密钥和哈希的无序映射
Unordered map for unique keys and hashing
我在一个库中工作,其中一些在这里无关紧要的元素必须用名称来标识(即值与名称相关联)。名称是用户的字符串,无论其内部表示形式是什么,都应该表现得透明。
- 名称是常量,并使用字符串文字进行初始化它们在编译时是已知的
- 使用不同字符串初始化的两个名称必须比较不同,无论其内部表示形式如何
- 名称的长度可以是任意的我的库没有设置任何限制
- 实际上,可能的名称不能有限制实现的约束不能影响接口,所以,我这边没有任何限制
考虑到频繁查找会发生,我考虑使用无序映射。
无序的关联容器通过数字(通常是std::size_t
类型)存储它们的元素,无论它们的类型是什么,这些数字是通过哈希函数获得的。这意味着:
- 对于可能值的数目小于或等于哈希值的数目的类型,不应该发生冲突
- 对于可能值的数量大于哈希值的数量的类型,可能会发生冲突,因为在哈希过程中会丢失一些数据
我考虑了两种解决方案。
按值哈希
使用数据本身来计算哈希值。注意事项:
-
可能在编译时计算由于名称是由字符串文字构造的,构造函数(可能是
constexpr
本身)和存储在类本身中的哈希值可以调用constexpr
哈希函数,以便稍后快速检索(由hasher对象) - 碰撞发生的频率是多少哪种算法最好
按顺序哈希
如本文所述,Boost.Log库维护一个全局(即静态)表,该表将名称与其哈希值相关联。可能的实施方式如下:
- 当(根据字符串文字)构造名称时,会查找表(执行精确比较)。
- 如果找不到,则在容器的末尾进行注册
- 其条目在表中的偏移量将成为其哈希值
注意事项:
-
非常慢对于构造的每个名称,都必须执行与注册名称一样多的字符串比较。这并不比传统的
std::map
好多少,是吗 - 线程不安全该表必须受到保护
- 在运行时强制执行
问题
-
在这些条件下使用无序映射是否正确用
std::map
会更好吗 - 如果1是"是",哪种方法更好,为什么Boost.Log中使用的那个似乎真的很低效,为什么要使用它而不是我解释的那个,即使在编译时字符串不一定是已知的
注意:尽管我可以使用gcc和clang提供的实验支持,但我还没有添加c++14
标签。请不要犹豫使用即将发布的规范中包含的功能。
在这种情况下使用无序地图是正确的吗?会吗不如使用std::map?
如果您不需要对条目进行排序,那么使用unordered_map
通常比使用map
更有效。由于两者都有一个几乎相同的接口,因此这当然很容易测量(您应该这样做)。
如果1为"是",则哪种方法是更好,为什么?Boost.Log中使用的那个似乎真的很低效,为什么使用它而不是其他我解释过的,即使字符串是在编译时不一定知道?
您应该更好地阅读Boost文档。我没有读过任何关于线性复杂度查找的文章。attribute_set
的描述表明使用了一个关联容器(我希望使用std::unordered_map,但您可以自己检查源代码)。文档中也明确提到了使用标识符而不是字符串的原因:
"使用标识符比使用字符串效率高得多。例如,复制不涉及动态内存分配,比较运算符非常轻。"
这在您的情况下是否有益取决于您使用这些数据结构的方式。由于您指出字符串标识符可以表示为字符串文字(但请考虑是否需要翻译这些字符串),因此您只需要传递一个指针来复制字符串标识符。然而,比较仍然比boost::attribute_name
s慢。
在这种情况下使用无序地图是正确的吗?会吗不如使用
std::map
?
虽然可能值的数量大于哈希值的数量的类型可能会发生冲突,但当它们发生冲突时,容器会注意到哈希值标识的bucket中已经有一个guest,并直接比较密钥。因此,不同的关键帧将永远不会碰撞。尝试使用一个总是返回固定值的哈希函数,看看插入键时会发生什么——它会变得慢,这就是哈希算法很重要的原因。
因此,使用std::unordered_map
是一个很好的选择,如果如您所述,将发生频繁查找,并且不需要排序。不过,正如D Drmmr建议的那样,你仍然应该根据std::map
来测量它。
如果1是"是",那么哪种方法更好,为什么?中使用的Boost.Log似乎真的很低效,为什么使用它而不是其他我解释过,即使字符串在编译时?
如果你担心不同的密钥因为哈希值相等而发生冲突,那么不要担心;如上所述,这不是问题。因此,您应该选择第一种方法,因为它允许编译时哈希,并且不会遇到第二种方法的所有问题。
一种可能的实现方式:
// You stated that names were constant and constructed from string literals.
// Borrowed from the example at http://en.cppreference.com/w/cpp/language/constexpr
class
name final
{
private:
const char * const
s; // string
const std::size_t
l; // length
public:
template<std::size_t N> constexpr
name
( const char (& s)[N] )
noexcept
: s( s ) , l( N-1 )
{ }
// Interface that enables hashing algorithms to operate on your class.
// If hashing is to happen at compile-time, the methods must be
// declared `constexpr`.
};
struct
hasher final
{
constexpr std::size_t
operator()
( const name & n )
const noexcept
{
return 0; // read below
}
};
您必须实现一个用于哈希算法的接口,才能访问name
类的底层数据。此外,如示例中所述,这些方法应该是constexpr
声明的;否则,就无法从启用constexpr
的哈希函数中调用它们。至于散列算法,有很多种,每种算法在某些情况下都是可行的。本页详细介绍了该主题,并介绍了X65599的一个实现,但该实现不使用constexpr
。你可以先尝试一下,看看它在你的情况下表现如何。
- 递归无序映射
- 正在将无序映射设置为无序映射的值
- 智能指针作为无序映射键,并通过引用进行比较
- 打印无序映射的第二个元素,即集合
- 由并发无序映射查找线程调用的函数是否安全?
- 将大数字(10-12 位数字)存储在无序映射中<字符串,整数>
- 使用无序映射在STL中存储键值对
- 为C++中的无序映射获取给定输入键的错误值
- 将大型对象存储在无序映射中是否效率低下
- 在无序映射的结构化绑定中推导类型
- 使用无序映射进行错误索引
- 将一个向量对放在一个无序映射与一个映射中
- 如何在c++中修改无序映射中的每个值
- C++:使用一对(cpp_int,int)整数作为无序映射中的键(其中cpp_int是boost多精度整数)
- 将 [] 索引到无序映射时出现段错误
- 以std::字符串作为关键字,按字典顺序对一个无序映射进行排序
- 无序映射存储桶的节点大小
- 在无序映射<字符串上使用 find(),向量<string>>与 C++ 中的有序映射花费相同的时间
- 以三个无符号字符为关键字的无序映射
- 无序映射比c++中的映射慢吗