【问题标题】:why IdentityHashMap uses linear probing for collision resolution为什么 IdentityHashMap 使用线性探测来解决冲突
【发布时间】:2013-06-16 06:04:06
【问题描述】:

正如我们在 java 集合框架中所知,Map 中的每个类都使用链式解决冲突,但 IdentityHashMap 使用线性探测来解决相同问题。

如果您看到 java 文档,它已经提到:

对于许多 JRE 实现和操作组合,此类将 产生比 HashMap 更好的性能(它使用链接而不是 线性探测)。

我的问题是:

  • 如果线性探测的性能更好,为什么实现者只对IdentityHashMap而不是所有Map实现使用线性探测 >

  • 为什么在线性探测链接中会有性能提升。

坦克。

【问题讨论】:

  • 答案在 javadoc 上。表现。看起来Map 的这种特殊实现是为那些对象标识足够且性能为标准的罕见情况而设计的。
  • @Andreas_D 你能告诉我这对性能有什么帮助吗?
  • @Andreas_D:这没有回答这个问题,因为它引出了一个明显的下一个问题:为什么在这种情况下性能更好,因为它总是正确的,线性探测比链接更好。是什么支撑着地球?一只乌龟! Well, then what is holding the tortoise up?
  • @Jason 那么他应该问那个可能更有意义的问题,并且不能仅通过查看课程附带的文档来回答。他询问了这种方法背后的原因,程序员告诉我们他们的原因。

标签: java algorithm data-structures collections map


【解决方案1】:

当您构建身份哈希映射时,不可能找到两个彼此相等但又不是同一个对象的实例。它还使用System.identityHashCode,它有碰撞的机会,这是IdentityHashMap 的设计者预先知道的,并且已知非常小。在这些“实验室”条件下,就性能而言,线性探测似乎是更好的选择。

我怀疑类库的设计者在“常规”哈希映射中使用链接而不是线性探测的原因是他们希望即使在哈希函数次优时也能保持良好的性能。

【讨论】:

  • +1 另外:“我怀疑类库的设计者在“常规”哈希映射中使用链接而不是线性探测的原因是他们希望即使在使用哈希函数时也能保持良好的性能是次优的。" 同意。
  • 这是使用开放寻址的一个很好的理由(或者更普遍地偏离其他哈希映射以减轻冲突)。但是,这听起来并不是专门使用线性探测的好理由:线性探测非常容易受到集群的影响(许多键散列到连续/附近的插槽)。例如,如果键 1..20 散列到连续槽,而第 21 个散列到与第一个槽相同的槽,则查找它(或散列到第一个槽的不存在的键)需要 20 次探测。并且很可能在典型的 JVM 集群上进行 AFAIK(哈希是地址,分配是线性的)。
【解决方案2】:

这可能会有所启发(取自Oracle 网站):

实现说明:这是一个简单的线性探针哈希表,如 Sedgewick 和 Knuth 的文本中所述。数组交替保存键和值。 (这对于大型表来说比使用单独的数组具有更好的局部性。)对于许多 JRE 实现和操作混合,这个类将产生比 HashMap 更好的性能(它使用链接而不是线性探测)。

虽然对于大多数实现来说,链接可能会更好,但并非对每个实现都如此。

EDIT也发现了这个,也许它不那么琐碎(取自here):

使用探测的动机是它比跟随链表要快一些,但只有当对值的引用可以直接放在数组中时才是正确的。这对于所有其他基于散列的集合是不切实际的,因为它们存储散列码以及值。这是出于效率的原因:get 操作必须检查它是否找到了正确的键,并且由于相等是一项昂贵的操作,因此首先检查它是否具有正确的哈希码是有意义的。当然,这种推理不适用于IdentityHashMap,它检查对象身份而不是对象相等性。

作为背景/说明,IdentityHashMap 与普通 HashMap 的不同之处在于,两个键仅在物理上是相同的对象时才被认为是相等的:标识而不是相等用于键比较。

编辑:有助于找到答案的讨论(来自下面的 cmets):

尝试:

但只有当对值的引用可以直接放在数组中时才是正确的。这对于所有其他基于散列的集合是不切实际的,因为它们存储散列码以及值。我有一个疑问,如果链表遍历比直接数组更昂贵,为什么 hashMap 不能将键、值和哈希码放入数组并使用线性探测?

威利斯:

可能是因为空间使用。这将在每个插槽中占用更多数据。我应该指出,虽然线性探测的遍历成本较低,但总查找操作的成本可能更高(并且更难以预测),因为线性探测经常受到集群的困扰,其中许多键具有相同的哈希值。正如@delnan 在另一条评论中所说,例如,如果键 1..20 散列到连续槽,并且第 21 个散列到与第一个槽相同的槽,则查找它(或查找散列到第一个插槽)需要 20 个探头。使用列表将需要更少的探测。进一步说明:由于 IdentityHashMap 比较键值的方式,发生冲突的机会非常小。因此,线性探测的主要弱点 - 导致聚集的碰撞 - 在很大程度上被避免,使其在此实现中更可取。

进一步澄清:由于IdentityHashMap比较键值的方式,发生冲突的机会非常小。因此,线性探测的主要弱点——导致聚集的碰撞——在很大程度上被避免了,这使得它在这个实现中更可取

【讨论】:

  • 这只是说这个类有时会产生更好的性能;它没有说明 why 线性探测与链接。大概答案“更好的性能”,但这会引发下一个明显的问题:为什么IdentityHashMap的预期使用中性能更好,因为这并不总是当一个人选择线性探测而不是各种链接方法时的情况。
  • @wlyles 编辑后我特别喜欢这个答案。 but that is only true when a reference to the value can be placed directly in the array. That isn't practical for all other hash-based collections, because they store the hash code as well as the value. 我有一个疑问,如果链表遍历比直接数组更昂贵,为什么 hashMap 不能将 key, value and hash code 放入数组并使用线性探测?谢谢!!!
  • @Trying 可能是因为空间使用。这将在每个插槽中占用更多数据。我应该指出,虽然 traversal 对线性探测的成本较低,但 total 查找操作的成本可能更高(并且更难以预测),因为线性探测经常受到以下因素的困扰聚类,其中许多键具有相同的哈希值。正如@delnan 在另一条评论中所说,For example, if keys 1..20 hash to consecutive slots, and the 21st hashes to the same slot as the 1st, lookup for it (or for a not-present key that hashes to the 1st slot) needs 20 probes. 使用列表将需要更少的探测
  • 进一步澄清:由于IdentityHashMap比较键值的方式,发生冲突的机会非常小。因此,线性探测的主要弱点——导致聚集的碰撞——在很大程度上被避免了,这使得它在这个实现中更受欢迎。
【解决方案3】:

From Docs:

实现说明:这是一个简单的线性探针哈希表,如 Sedgewick 和 Knuth 的文本中所述。该数组交替保存键和值。 (与使用单独的数组相比,这对于大型表具有更好的局部性。) 对于许多 JRE 实现和操作混合,此类将产生比 HashMap(使用链接而不是线性探测)更好的性能。

原因是 这个类会比 HashMap 产生更好的性能。

【讨论】:

  • 这并不能回答线性探测与链接的为什么
  • For many JRE implementations and operation mixes, this class will yield better performance than HashMap 这不是一个真正的答案,而且这个答案充其量是微不足道的,因为它只是一个链接。
  • @Jason 当然可以:与 HashMap 相比性能更好。只是 RTFM。
  • 我相信This has better locality for large tables than does using separate arrays 是这个答案的核心
  • @JanDvorak 不,该行是为了不使用两个数组。现在的编辑使答案更加错误。
猜你喜欢
  • 2011-01-21
  • 2017-08-30
  • 2023-04-04
  • 1970-01-01
  • 2010-12-22
  • 2015-07-25
  • 1970-01-01
  • 2016-04-14
  • 2019-12-22
相关资源
最近更新 更多