【问题标题】:hashCode implementation strategieshashCode 实现策略
【发布时间】:2012-04-16 02:23:02
【问题描述】:

一些主要的 JVM 类(例如 StringList implementations)通过为与 equals 方法相关的每个 field_n 返回 Σ 31^n * field_n.hashCode() 来实现 equals。此外,Joshua Bloch 在 Effective Java(第 9 项)中推荐了这种方法。

但是,Map.Entry implementations 等其他类遵循不同的规则。例如,Map.Entry 文档指出 Map.Entry 的哈希码应该是

 (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
 (e.getValue()==null ? 0 : e.getValue().hashCode())

这有时在哈希表中使用是不切实际的,因为:

  • 所有具有相同键值的条目的哈希码为0,
  • 两个条目 e1 和 e2 使得 e1.key = e2.value 和 e1.value = e2.key 具有相同的哈希码。

为什么Java 选择Map.Entry hashCode 的这个实现规范而不是例如31 * (e.getKey()==null ? 0 : e.getKey().hashCode()) + (e.getValue()==null ? 0 : e.getValue().hashCode())

编辑 1:

为了帮助解决问题,这里有一个有用的代码示例,如果许多条目具有相同的键和值,则由于哈希冲突导致结果性能非常差。

此方法计算不同地图条目的频率(使用 Guava 的 Multiset)。

public static <K, V> Multiset<Map.Entry<K, V>> computeEntryCounts(
        Iterable<Map<K, V>> maps) {
    ImmutableMultiset.Builder<Map.Entry<K, V>> result = ImmutableMultiset.builder();
    for (Map<K, V> map : maps) {
        for (Map.Entry<K, V> entry : map.entrySet()) {
            result.add(entry);
        }
    }
    return result.build();
}

【问题讨论】:

  • 您的实现不会影响键和值为 null 的输出。此外,在 Java 中的 Map 中只有 一个 条目具有一个键,一个键在任何 Map 实现中仅出现 一次
  • 我知道,我假设 HashMap 的键是 Map.Entry 的实例。如果您想计算跨多个 Map 的每个键值条目的总数,就会发生这种情况。
  • 我看不出这将如何影响这种情况,除非您想将所有地图中的所有 Map.Entry 放在一个地图中以对它们进行计数,但这显然是错误的。
  • 虽然Map.EntryhashCode 定义(以及由此而来的hashCodeMap 定义)确实存在很大问题,但很难找到@ 987654337@ 或 Map.Entry 可用作另一个 Map 的键(至少在结构良好的代码中)。

标签: java hashcode


【解决方案1】:

我怀疑是否有充分的理由——我认为这只是一个疏忽——但这并不是一个严重的问题。只有当您有 HashSet&lt;Map.Entry&lt;T,T&gt;&gt;HashMap&lt;Map.Entry&lt;T,T&gt;,V&gt; 时才会出现,这并不常见。 (编辑添加:或者,正如 Joachim Sauer 在下面指出的那样,HashSet&lt;Map&lt;T,T&gt;&gt;HashMap&lt;Map&lt;T,T&gt;,V&gt; — 也不常见。)

注意HashMap&lt;K,V&gt; 确实使用Map.Entry&lt;K,V&gt;.hashCode(),因为它通过键查找条目,所以它只使用K.hashCode()

【讨论】:

  • 由于 Map.hashCode() 是根据其 Map.Entry 元素的 hashCode 定义的,因此这也出现在 Map&lt;Map&lt;K,V&gt;,V2&gt; 中。或Set&lt;Map&lt;K,V&gt;&gt;。但是这些也很容易避免(出于可读性和设计的原因,通常也应该避免)。
  • 遇到这个问题只需要someHashSet.addAll(someMap.entrySet()) 或类似Guava issue 的东西。所有像“a”=>“a”这样的条目都保证会发生冲突,并且性能保证很糟糕(或者至少不如自 JDK 8 以来的最佳)。你很少遇到它,但当你遇到它时,它会很糟糕。
【解决方案2】:

我个人的猜测是,hashCode 也应该很快。

由于允许您覆盖 hashCode,因此没有问题。 当您知道更适合您的案例的拟合算法时更改它

【讨论】:

  • 抱歉,这是不对的。 Map.Entry 是一个接口。它没有描述 it 实现但子类可以覆盖的 default 算法;相反,它规定了实施者必须实施的特定算法。
  • 当 sun/oracle 在所有情况下都需要某种实现时,它们必须使用抽象类而不是接口。
猜你喜欢
  • 1970-01-01
  • 2021-11-16
  • 2021-07-16
  • 1970-01-01
  • 2014-10-18
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
  • 1970-01-01
相关资源
最近更新 更多