HashSet的迭代顺序

如果添加到java.util.HashSet的每个对象都以确定的方式实现Object.equals()和Object.hashCode(),则对于添加的每个相同元素集,HashSet上的迭代顺序保证相同, 而不管他们被加入的顺序?

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

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

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

绝对不。

每当您遇到存储桶冲突时,插入顺序会直接影响迭代顺序:

当两个元素在同一个桶中结束时,插入的第一个元素也将是迭代期间返回的第一个元素,至少如果碰撞处理和迭代的实现很简单(并且Sun的java.util.HashMap那个是)

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

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

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

根据javadoc:

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

和方法iterator

返回此set中元素的迭代器。 元素按特定顺序返回。

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

想要确认/提前评论。 简而言之, 不要以一致的顺序依赖于HashSet迭代 。 这可能并将在您的系统中引入错误。

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

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

并使用LinkedHashSet修复它。

感谢早期的海报:)

不,这不能保证。

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

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

永远不要对你放入HashSet的任何东西的迭代顺序做出假设,因为它的契约明确地表明你不能以任何方式指望它。 如果要维护自然排序顺序,如果要维护插入顺序或TreeSet,请使用LinkedHashSet 。

显示的订单对象将取决于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 set = new HashSet(size, loadFactor); for(int i=0;i<=100;i+=10) set.add(i); System.out.println("new HashSet("+size+", "+loadFactor+") adding 0,10, ... 100 => "+set); } 

版画

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

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

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

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

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