【问题标题】:Iteration order of HashSetHashSet的迭代顺序
【发布时间】:2011-02-11 20:57:33
【问题描述】:

如果添加到 java.util.HashSet 的每个对象都以确定性方式实现 Object.equals() 和 Object.hashCode(),那么对于添加的每组相同的元素,HashSet 上的迭代顺序是否保证相同, 无论它们的添加顺序如何?

额外问题:如果插入顺序也相同怎么办?

(假设 Sun JDK6 具有相同的 HashSet 初始化。)

编辑:我最初的问题不清楚。这不是关于 HashSet 的一般合同,而是 Sun 在 JDK6 中的 HashSet 实现提供了关于确定性的保证。它本质上是非确定性的吗?是什么影响了它的迭代器使用的顺序?

【问题讨论】:

  • 我认为 Michael Borgwardt 指出了这一点:插入顺序会影响碰撞行为。 Péter Török 关于初始化(例如大小和负载因子)的观点也很重要。除此之外,它将是确定性的。相同的 JVM,相同的初始化,相同的顺序?它怎么可能不是确定性的?我查看了 JDK6 代码,它显然是确定性的 - 没有使用 Math.random() !!!
  • 可以编写使用 Math.random() 的确定性程序。对于不使用 Math.random() 的非确定性程序也是如此。

标签: java algorithm collections hashset


【解决方案1】:

绝对不是。

无论何时发生桶碰撞,插入顺序都会直​​接影响迭代顺序:

当两个元素最终位于同一个存储桶中时,插入的第一个元素也将是迭代期间返回的第一个元素,至少在碰撞处理和迭代的实现很简单的情况下(以及 Sun 的 java.util.HashMap 中的元素)是)

【讨论】:

  • 很好的答案。我编辑了一个小问题:如果插入顺序也保持不变怎么办?换句话说:在“标准” java.util.HashMap 的实现中是否存在任何固有的不确定性?
  • @eljenso:我很确定没有 - 但我不知道如何最终证明这一点。
  • @eljenso 如果今天没有,明天可能会有,如果规范(Hashmap 文档)没有另外说明。
【解决方案2】:

这样的事情没有“官方”保证。我想说,对于相同 HashSet 实现的实例,以相同的方式初始化,这很可能是正确的。但是,例如,我已经看到了迭代顺序在 Java 5 和 6 之间不同的情况。

此外,由于重新散列,相同 HashSet 实现的实例可能不同,初始化为不同的大小。 IE。如果你有 100 个元素和两组,一组初始化的大小大于 100,另一组的大小要小得多,第二个将被重新分配,并且在填充时它的元素会重新散列几次。这可能会导致映射到同一存储桶的元素以不同的顺序添加(并因此迭代)。

在 Java4 及更高版本中,LinkedHashSet 保证迭代顺序将是插入其元素的顺序。

【讨论】:

    【解决方案3】:

    想要确认/支持早期的 cmets。简而言之,不要依赖 HashSet 以一致的顺序迭代。这会并且会在您的系统中引入错误。

    我们刚刚发现并修复了 HashSet 中迭代顺序不一致的问题,即使是:

    • 相同的插入顺序。
    • 具有有效 equals() 和 hashCode() 方法的类的对象。

    并使用 LinkedHashSet 修复它。

    感谢之前的海报:)

    【讨论】:

    • 这里有进一步的讨论,表明即使在完全“确定性”的情况下,GC 位于单独的线程中也会引入不可预测性:stackoverflow.com/questions/4418896/…
    • +1 用于评论结果,即使使用相同的插入顺序,也是讨论的好补充
    【解决方案4】:

    根据 javadoc:

    这个类实现了 Set 接口,由哈希表支持 (实际上是一个 HashMap 实例)。它 不保证 集合的迭代顺序;在 特别是,它不保证 订单将保持不变 时间。 [...] 此类的迭代器方法返回的迭代器是快速失败的:如果集合在迭代器创建后的任何时间被修改

    还有iterator的方法:

    返回元素的迭代器 在这一套。返回的元素 没有特别的顺序。

    所以我认为你不能做出这样的假设。

    【讨论】:

    • 我原来的问题不清楚。对此感到抱歉。您的答案在一般意义上是正确的。
    【解决方案5】:

    永远不要对您放入 HashSet 的任何内容的迭代顺序做出假设,因为它的合同明确规定您不能以任何方式依赖它。如果要保持插入顺序,请使用 LinkedHashSet,如果要保持自然排序顺序,请使用 TreeSet。

    【讨论】:

      【解决方案6】:

      出现的订单对象将取决于 HashSet 的最终桶数。通过更改负载因子和/或初始容量,您可以更改元素的最终顺序。

      在下面的示例中,您可以看到这些配置各自以不同的顺序产生。

      public static void main(String...args) throws IOException {
          printOrdersFor(8, 2);
          printOrdersFor(8, 1);
          printOrdersFor(8, 0.5f);
          printOrdersFor(32, 1f);
          printOrdersFor(64, 1f);
          printOrdersFor(128, 1f);
      }
      
      public static void printOrdersFor(int size, float loadFactor) {
          Set<Integer> set = new HashSet<Integer>(size, loadFactor);
          for(int i=0;i<=100;i+=10) set.add(i);
          System.out.println("new HashSet<Integer>("+size+", "+loadFactor+") adding 0,10, ... 100 => "+set);
      }
      

      打印

      new HashSet<Integer>(8, 2.0) adding 0,10, ... 100 => [0, 50, 100, 70, 40, 10, 80, 20, 90, 60, 30]
      new HashSet<Integer>(8, 1.0) adding 0,10, ... 100 => [0, 50, 100, 70, 20, 80, 10, 40, 90, 30, 60]
      new HashSet<Integer>(8, 0.5) adding 0,10, ... 100 => [0, 100, 70, 40, 10, 50, 20, 80, 90, 30, 60]
      new HashSet<Integer>(32, 1.0) adding 0,10, ... 100 => [0, 100, 70, 40, 10, 50, 80, 20, 90, 60, 30]
      new HashSet<Integer>(64, 1.0) adding 0,10, ... 100 => [0, 70, 10, 80, 20, 90, 30, 100, 40, 50, 60]
      new HashSet<Integer>(128, 1.0) adding 0,10, ... 100 => [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
      

      【讨论】:

        【解决方案7】:

        不,这不能保证。

        首先,不同的JVM实现HashSet算法的方式可能不同(只要符合HashSet规范),所以在不同的JVM上会得到不同的结果。

        其次,该算法在构建不同的桶(哈希表算法的一部分)时可能依赖于非确定性因素。

        【讨论】:

        • 我使用的是同一个 JVM。我特别提到所有哈希码都是确定性的(即 Object.hashCode() 总是以有意义和确定性的方式覆盖)。
        【解决方案8】:

        我确信 Java 开发人员希望您假设答案是否定的。特别是,对于哈希表,为什么它们会让其他不需要此属性的其他人更慢以保证哈希冲突的对象(相同的 hashCode % 大小)以相同的顺序被观察到,而不管它们的顺序如何放进去吗?

        【讨论】:

          【解决方案9】:

          不能做出这样的假设。 javadoc 说:

          这个类实现了 Set 接口,由哈希表支持 (实际上是一个 HashMap 实例)。它 不保证 集合的迭代顺序;在 特别是,它不保证 订单将保持不变 时间。

          您可以获得的最接近的方法是使用LinkedHashSet,它维护插入顺序。

          【讨论】:

            猜你喜欢
            • 2015-11-09
            • 1970-01-01
            • 1970-01-01
            • 2016-01-18
            • 2012-12-28
            • 2012-10-31
            • 1970-01-01
            • 1970-01-01
            • 2013-02-19
            相关资源
            最近更新 更多