簡體   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