2010-04-24 13 views
15

Wenn jedes Objekt, das zu einem java.util.HashSet hinzugefügt wird, Object.equals() und Object.hashCode() deterministisch implementiert, ist die Iterationsreihenfolge über das HashSet garantiert identisch für jeden identischen Satz von Elementen hinzugefügt, unabhängig der Reihenfolge, in der sie hinzugefügt wurden?Iterationsreihenfolge von HashSet

Bonusfrage: Was ist, wenn der Anzeigenauftrag identisch ist?

(Unter der Annahme, Sun JDK6 mit gleicher HashSet Initialisierung.)

Edit: Meine ursprüngliche Frage nicht klar war. Es geht nicht um den allgemeinen Vertrag von HashSet, sondern darum, was Suns Implementierung von HashSet in JDK6 als Garantien für Determinismus bietet. Ist es von Natur aus nicht deterministisch? Was beeinflusst die Reihenfolge des Iterators?

+0

denke ich, Michael Borgwardt Nägel es: Einsetzen Ordnung Kollisionsverhalten bewirkt. Péter Töröks Argumentation über die Initialisierung (z. B. Größe und Lastfaktor) ist ebenfalls wichtig. Ansonsten wird es deterministisch sein. Gleiche JVM, gleiche Initialisierung, gleiche Reihenfolge? Wie könnte es möglicherweise NICHT deterministisch sein? Ich habe den JDK6-Code angeschaut und es ist eindeutig deterministisch - kein Einsatz von Math.random() drin !!! –

+1

Es ist möglich, deterministische Programme zu schreiben, die Math.random() benutzen. Gleiches gilt für nicht-deterministische Programme, die Math.random() nicht verwenden. – whiskeysierra

Antwort

15

Absolut nicht.

Der Auftrag beeinflusst direkt die Iteration, um, wenn Sie einen Eimer Kollision haben:

Wenn zwei Elemente in dem gleichen Eimer am Ende, die ersten, die auch eingesetzt worden ist, wird der erste, der während der Iteration zurückgebracht werden, um zumindest dann, wenn die Implementierung der Kollisionshandhabung und Iteration unkompliziert ist (und die in Suns java.util.HashMap ist)

+1

Große Antwort. Ich habe in einer kleinen Bonusfrage editiert: Was ist, wenn der Anzeigenauftrag gleich bleibt? Mit anderen Worten: Gibt es etwas, das bei der Implementierung des "Standards" java.util.HashMap inhärent nicht deterministisch ist? – eljenso

+0

@eljenso: Ich bin mir ziemlich sicher, dass es nicht ist - aber ich sehe nicht, wie ich das schlüssig beweisen kann. –

+0

@eljenso Wenn es heute nicht gibt, könnte es morgen sein, wenn die Spezifikation (Hashmap doc) nicht anders sagt. – bacar

1

Nein, dies ist nicht garantiert.

Erstens kann eine andere JVM den HashSet-Algorithmus anders implementieren (solange er der HashSet-Spezifikation entspricht), sodass Sie unterschiedliche Ergebnisse auf verschiedenen JVMs erhalten.

Zweitens kann der Algorithmus auf nicht-deterministischen Faktoren beruhen, wenn er die verschiedenen Buckets (Teil des Hash-Tabellen-Algorithmus) erstellt.

+0

Ich verwende die gleiche JVM. Ich erwähne ausdrücklich, dass alle Hash-Codes deterministisch sind (d. H. Object.hashCode() wird immer auf eine sinnvolle und deterministische Weise überschrieben). – eljenso

6

Gemäß der javadoc:

Diese Klasse implementiert die Set Schnittstelle, unterstützt durch eine Hash-Tabelle (eigentlich eine HashMap Instanz). Es macht keine Garantien bezüglich der Iterationsreihenfolge des Satzes; in insbesondere garantiert es nicht, dass die Reihenfolge über Zeit konstant bleibt. [...] Die Iteratoren von Klasse dieser Iteratormethode zurückgekehrt sind ausfall schnell: wenn der Satz jederzeit geändert wird, nachdem der Iterator

erstellt

Und die Methode iterator:

Gibt einen Iterator über die Elemente in diesem Set zurück. Die Elemente werden in keiner bestimmten Reihenfolge zurückgegeben.

Also ich glaube nicht, dass Sie eine solche Annahme machen können.

+0

Meine ursprüngliche Frage war nicht klar. Das tut mir leid. Ihre Antwort ist zwar in einem allgemeinen Sinn richtig. – eljenso

12

Es gibt keine "offizielle" Garantie für so etwas. Ich würde sagen, dass es wahrscheinlich für Instanzen der gleichen HashSet-Implementierung gilt, die auf die gleiche Weise initialisiert wurde. Aber ich habe Fälle gesehen, in denen die Iterationsreihenfolge beispielsweise zwischen Java 5 und 6 verschieden ist.

Es kann auch für Instanzen der gleichen HashSet-Implementierung anders sein, die mit unterschiedlicher Größe initialisiert wurden, weil sie erneut generiert wurden. I.e. Wenn Sie 100 Elemente und zwei Mengen haben, eine mit einer Größe größer als 100 initialisiert, die andere mit einer viel kleineren Größe, wird die zweite neu zugewiesen und ihre Elemente werden beim Auffüllen mehrmals aufgeräumt. Dies kann dazu führen, dass Elemente, die demselben Bucket zugeordnet sind, in anderer Reihenfolge hinzugefügt (und somit iteriert) werden.

In Java4 und höher haben Sie LinkedHashSet, die garantiert, dass die Iterationsreihenfolge die Reihenfolge ist, in der seine Elemente eingefügt wurden.

0

Ich bin mir sicher, dass die Java-Entwickler möchten, dass Sie die Antwort "Nein" annehmen. Insbesondere für Hashtabellen würden sie es für alle anderen langsamer machen, die diese Eigenschaft nicht benötigen, um zu garantieren, dass Objekte, deren Hashes kollidieren (identische HashCode% size), in derselben Reihenfolge beobachtet werden, unabhängig von ihrer Reihenfolge einstellen?

0

Eine solche Annahme kann nicht gemacht werden. Die javadoc sagt, dass:

Diese Klasse umfasst die Set -Schnittstelle implementiert, unterstützt durch eine Hash-Tabelle (eigentlich eine HashMap Instanz). Es macht keine Garantien bezüglich der Iterationsreihenfolge des Satzes; in insbesondere garantiert es nicht, dass die Reihenfolge über Zeit konstant bleibt.

Am nächsten kommt man mit einer LinkedHashSet, die den Anzeigenauftrag beibehält.

1

Niemals Annahmen über die Iterationsreihenfolge von irgendetwas, das Sie in ein HashSet setzen, machen, da sein Vertrag explizit besagt, dass Sie sich in keiner Weise darauf verlassen können. Verwenden Sie LinkedHashSet, wenn Sie den Anzeigenauftrag beibehalten möchten, oder TreeSet, wenn Sie eine natürliche Sortierreihenfolge beibehalten möchten.

6

Wollte frühere Kommentare bestätigen/upvote. Kurz gesagt, Verlassen Sie sich nicht auf die HashSet-Iteration in konsistenter Reihenfolge. Dies kann und wird Fehler in Ihrem System einführen.

Wir haben gerade und ein Bug behoben, der Iterationsreihenfolge in HashSet inkonsistent war sogar mit:

  • Identische Auftrag.
  • Objekte einer Klasse mit einer gültigen Methode equals() und hashCode().

Und es mit Hilfe von LinkedHashSet behoben.

Dank des früheren Poster :)

+0

Hier ist eine weitere Diskussion, die darauf hinweist, dass der GC in einem separaten Thread selbst in vollständig "deterministischen" Szenarios unvorhersehbar ist: http://stackoverflow.com/questions/4418896/what- causes-the-sightly-unpredictable-ordering-of -the-iterator-for-the-java-ut – chaotic3quilibrium

+0

+1 für das Kommentieren der Ergebnisse, auch wenn Sie die identische Reihenfolge verwenden, gute Ergänzung zur Diskussion – nerdherd

1

Die Reihenfolge Objekte auf der endgültigen Anzahl der Schaufeln des HashSet hängen erscheinen. Durch Ändern des Ladefaktors und/oder der Anfangskapazität können Sie die Reihenfolge der Elemente ändern.

Im folgenden Beispiel sehen Sie, dass diese Bestätigungen jeweils in einer anderen Reihenfolge angezeigt werden.

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); 
} 

druckt

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]