HashSet的迭代顺序

时间:2010-04-24 13:26:43

标签: java algorithm collections hashset

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

奖金问题:如果插入顺序相同怎么办?

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

编辑:我原来的问题不明确。它不是关于HashSet的一般契约,而是Sun在JDK6中对HashSet的实现提供了关于确定性的保证。它本质上是非确定性的吗?是什么影响了迭代器使用的顺序?

9 个答案:

答案 0 :(得分:18)

绝对不是。

只要您遇到存储桶冲突,插入顺序就会直接影响迭代顺序:

当两个元素在同一个桶中结束时,插入的第一个元素也将是迭代期间返回的第一个元素,至少如果碰撞处理和迭代的实现是直截了当的(并且是Sun的{{1是}

答案 1 :(得分:13)

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

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

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

答案 2 :(得分:8)

根据javadoc:

  

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

方法iterator

  

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

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

答案 3 :(得分:7)

想要确认/提前评论。简而言之,不依赖于HashSet迭代的一致顺序。这会导致系统中出现错误。

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

  • 相同的广告订单。
  • 具有有效equals()和hashCode()方法的类的对象。

使用LinkedHashSet修复它。

感谢早期的海报:)

答案 4 :(得分:2)

永远不要对你放入HashSet的任何东西的迭代顺序做出假设,因为它的契约明确表示你不能以任何方式依赖它。如果要维护广告订单,请使用LinkedHashSet;如果要维护自然排序顺序,请使用TreeSet

答案 5 :(得分:1)

不,这不能保证。

首先,不同的JVM可能以不同的方式实现HashSet算法(只要它符合HashSet规范),因此您将在不同的JVM上获得不同的结果。

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

答案 6 :(得分:1)

显示的订单对象将取决于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 :(得分:0)

我确信Java开发人员希望您认为答案是“不”。特别是,对于散列表,为什么它们会让那些不需要这个属性的其他人保证它的速度变慢,以保证以相同顺序观察到散列冲突(相同hashCode%size)的对象,而不管它们的顺序如何投入?

答案 8 :(得分:0)

无法做出这样的假设。 javadoc说:

  

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

您最接近的是使用LinkedHashSet来维护广告订单。