【问题标题】:clarifying facts behind Java's implementation of HashSet/HashMap阐明 Java 实现 HashSet/HashMap 背后的事实
【发布时间】:2014-01-17 22:40:34
【问题描述】:

1。 我了解不同的哈希映射机制以及处理键冲突的方式(开放寻址 - 线性/二次探测、链接、可扩展哈希等。HashSet/HashMap 使用哪一个?

2。 我意识到一个好的 HashMap 依赖于一个好的散列函数。 Java 的 HashSet/HashMap 如何对对象进行哈希处理?我知道有一个哈希函数,但到目前为止,对于字符串,我不需要实现它。如果我现在想对我创建的 Java 对象进行哈希处理,我需要实现哈希函数吗?还是 Java 有创建哈希码的内置方法?

我知道不能依赖默认实现,因为它基于非恒定的内存地址散列函数。

【问题讨论】:

  • “我知道不能依赖默认实现,因为它基于非恒定的内存地址散列函数。”实际上,我今天看到了一个较早的问题,涉及到这一点。我认为它所说的是在默认实现中,它将使用对象在首次调用 hashCode 时所具有的内存地址,并缓存必要的位以供以后使用(这样如果内存位置已更改)
  • java 中的所有对象都有一个内置的 hashCode 方法。 docs.oracle.com/javase/7/docs/api/java/lang/…
  • 是的,我们开始:stackoverflow.com/a/20249790/1476062 涉及hashCode 以及它如何与移动的对象混合。
  • 另外,我之前的评论有误导性,它没有直接使用内存地址。
  • 这一切都可以通过谷歌搜索轻松回答。你会得到很多指向 SO 问题的点击,然后总是有 GrepCode 来查看源代码。

标签: java hash hashmap hashset


【解决方案1】:

问题 1)

Java HashMap 实现使用链接实现来处理冲突。把它想象成一个链表数组。

问题 2

Object 具有 equals 和 hashCode 的默认实现。 equals 实现为 return this == otherhashcode (就所有意图和目的而言)实现为为每个实例分配一个随机标识符并将其用作 hashCode

作为 Java extends Object 中的所有类,它们都继承了这些实现。

默认情况下,某些类会覆盖这些实现。正如您所提到的,String 是一个很好的例子。另一个是集合 API 中的类 - 所以ArrayList 基于它包含的元素实现这些方法。

就实现一个好的hashCode 而言,这有点像黑暗艺术。 Here's a pretty good summary 最佳实践。

您的最终评论:

我知道不能依赖默认实现,因为它基于非恒定的内存地址散列函数。

这是正确的。 hashCode 的默认实现是常量,因为它是方法契约的一部分。来自the Javadoc

每当在同一对象上多次调用它时 执行 Java 应用程序时,hashCode 方法必须一致 返回相同的整数,只要不使用 equals 中的信息 对象上的比较被修改。这个整数不需要保留 从应用程序的一次执行到另一次执行一致 相同的应用程序。

【讨论】:

    【解决方案2】:

    阅读the source code for HashMap,您可以自己回答其中的许多问题。

    (提示:您通常可以使用 Google 找到 Java SE 类的源代码;例如,搜索“java.util.HashMap source”。)

    我了解不同的哈希映射机制以及处理密钥冲突的方式(开放寻址 - 线性/二次探测、链接、可扩展哈希等。HashSet/HashMap 使用哪一个?

    链接。请参阅源代码。 (我链接到的版本中的第 154 行)。

    Java 的 HashSet/HashMap 如何对对象进行哈希处理?

    它没有。调用对象的hashCode 方法来执行此操作。请参阅源代码。 (第 360 行)。

    如果您查看代码,您会发现一些有趣的皱纹:

    • 代码(在我链接到的版本中)使用特殊方法对字符串进行哈希处理。 (这似乎是为了允许在平台级别“调整”字符串的散列。我没有深入研究这个......)

    • Object.hashCode() 调用返回的哈希码被进一步“加扰”以减少冲突的机会。 (阅读评论!)

    如果我现在想对我创建的 Java 对象进行哈希处理,我需要实现哈希函数吗?

    你可以这样做。

    您是否需要这样做取决于您如何为班级定义equals。具体来说,Java 的HashMapHashSet 及相关类对hashcode()equals(Object) 提出以下要求:

    1. 如果a.equals(b)a.hashCode() == b.hashCode()
    2. 虽然aHashSet 中或者是HashMap 中的键,但a.hashCode() 返回的值不得更改。
    3. 如果!a.equals(b),那么a.hashCode() == b.hashCode() 的概率应该很低,特别是如果ab 可能是应用程序的哈希键。

    (出于性能原因的最后一个要求。如果你有一个“差”的哈希函数,导致不同的键很可能哈希相同的哈希码,你会遇到很多冲突。哈希链会变得不平衡,你不会获得哈希表操作通常预期的平均O(1) 性能。在最坏的情况下,性能将是O(N);即相当于链表的线性搜索。)

    或者 Java 是否有内置的方法来创建哈希码?

    每个类都从Object 继承一个默认的hashCode() 方法(除非它被覆盖)。它使用所谓的“身份哈希码”;即基于对象身份(其引用)的哈希值。这与equals(Object) 的默认实现相匹配......它只是使用== 来比较引用。

    我知道不能依赖默认实现,因为它基于非恒定的内存地址散列函数。

    这是不正确的。

    默认的hashCode() 方法返回“身份哈希码”。这通常基于对象在某个时间点的内存地址,但它不是对象的内存地址。

    特别是,如果一个对象被垃圾收集器移动,它的“身份哈希码”保证不会改变。是的。没错,它不会改变……即使对象被移动了!

    (他们如何有效地实现这一点相当聪明。有关详细信息,请参阅https://stackoverflow.com/a/3796963/139985。)

    底线是默认的Object.hashCode() 方法满足我上面列出的所有要求。它可以值得信赖。

    【讨论】:

      猜你喜欢
      • 2012-07-25
      • 2023-03-07
      • 2016-10-12
      • 1970-01-01
      • 2015-07-06
      • 2017-10-10
      • 1970-01-01
      • 2014-03-05
      相关资源
      最近更新 更多