繁体   English   中英

std::unordered_map 是如何实现的

[英]How std::unordered_map is implemented

c ++ unordered_map 碰撞处理,调整大小和重新散列

这是我之前提出的一个问题,我已经看到我对 unordered_map 的实现方式有很多困惑。 我相信很多其他人跟我一样有这种困惑。 根据我在没有阅读标准的情况下所知道的信息:

每个 unordered_map 实现都将一个链表存储到桶数组中的外部节点......不,这根本不是实现最常见用途的哈希映射的最有效方法。 不幸的是, unordered_map 规范中的一个小“疏忽”几乎需要这种行为。 所需的行为是元素的迭代器在插入或删除其他元素时必须保持有效

我希望有人可以解释实现以及它如何符合 C++ 标准定义(在性能要求方面),如果它真的不是实现哈希映射数据结构的最有效方法,如何改进它?

该标准有效地要求std::unordered_setstd::unordered_map - 以及它们的“多”兄弟 - 使用 开放散列又名单独链接,这意味着一个桶数组,每个桶都持有链表的头部†。 这个要求很微妙:它是以下结果的结果:

如果没有链接,这将是不切实际的,因为与哈希表实现的其他主要类别(封闭哈希,开放寻址load_factor()load_factor()变得势不可挡,因为load_factor() ]( https://en.cppreference.com/w/cpp/container /unordered_map/load_factor ) 接近 1。

参考:

23.2.5/15:如果(N+n) < z * B ,则insertemplace成员不应影响迭代器的有效性,其中N是插入操作之前容器中元素的数量, n是插入的元素, B是容器的桶数, z是容器的最大负载因子。

在 23.5.4.2/1 的构造函数的效果中: max_load_factor()返回1.0

† 为了在不传递任何空桶的情况下允许最佳迭代,GCC 的实现将带有迭代器的桶填充到一个包含所有值的单链表中:迭代器指向该桶元素之前的元素,因此next指针可以是如果擦除存储桶的最后一个值,则重新连接。

关于你引用的文字:

不,这根本不是为大多数常见用途实现哈希映射的最有效方法。 不幸的是, unordered_map 规范中的一个小“疏忽”几乎需要这种行为。 所需的行为是元素的迭代器在插入或删除其他元素时必须保持有效

没有“监督”......所做的事情是非常深思熟虑的,并且是在充分意识到的情况下完成的。 的确,其他的妥协可能已经被击中,但开放散列/链接方法对于一般用途来说是一个合理的妥协,它可以合理优雅地处理来自平庸散列函数的冲突,对于小的或大的键/值类型不会太浪费,并处理任意多的insert / erase对,而不会像许多封闭散列实现那样逐渐降低性能。

作为意识的证据,来自Matthew Austern 的提议

我不知道在通用框架中任何令人满意的开放寻址实现。 开放寻址存在许多问题:

• 有必要区分空缺职位和被占用职位。

• 要么将哈希表限制为具有默认构造函数的类型,并提前构造每个数组元素,要么维护一个数组,其中一些元素是对象,而另一些元素是原始内存。

• 开放寻址使冲突管理变得困难:如果您要插入一个哈希码映射到已占用位置的元素,您需要一个策略来告诉您下一步尝试的位置。 这是一个已解决的问题,但最著名的解决方案是复杂的。

• 当允许擦除元素时,冲突管理尤其复杂。 (有关讨论,请参阅 Knuth。)标准库的容器类应该允许擦除。

• 开放寻址的冲突管理方案倾向于假设一个固定大小的数组,最多可容纳 N 个元素。 标准库的容器类应该能够在插入新元素时根据需要增长,直至达到可用内存的限制。

解决这些问题可能是一个有趣的研究项目,但是,在缺乏 C++ 上下文中的实现经验的情况下,将开放寻址容器类标准化是不合适的。

特别是对于数据小到可以直接存储在桶中的仅插入表、未使用桶的方便标记值和良好的散列函数,封闭散列方法可能大约快一个数量级并使用更少的内存,但是这不是通用目的。

对哈希表设计选项及其含义的完整比较和阐述对于 SO 来说是题外话,因为它太宽泛而无法在此处正确解决。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM