繁体   English   中英

HashSet的迭代顺序

[英]Iteration order of 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修复它。

感谢早期的海报:)

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

不,这不能保证。

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

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

显示的订单对象将取决于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]

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM