Java HashSet vs HashMap

Ich verstehe, dass HashSet auf der HashMap Implementierung basiert, aber verwendet wird, wenn Sie einen einzigartigen Satz von Elementen benötigen. Warum also im nächsten Code, wenn wir dieselben Objekte in die Map setzen und setzen, haben wir die Größe beider Collections gleich 1? Sollte die Kartengröße nicht 2 sein? Wenn die Größe beider Sammlungen gleich ist, sehe ich keinen Unterschied in der Verwendung dieser beiden Sammlungen.

  Set testSet = new HashSet(); Map testMap = new HashMap(); SimpleObject simpleObject1 = new SimpleObject("Igor", 1); SimpleObject simplObject2 = new SimpleObject("Igor", 1); testSet.add(simpleObject1); testSet.add(simplObject2); Integer key = new Integer(10); testMap.put(key, simpleObject1); testMap.put(key, simplObject2); System.out.println(testSet.size()); System.out.println(testMap.size()); 

Die Ausgabe ist 1 und 1.

 SimpleObject code public class SimpleObject { private String dataField1; private int dataField2; public SimpleObject(){} public SimpleObject(String data1, int data2){ this.dataField1 = data1; this.dataField2 = data2; } public String getDataField1() { return dataField1; } public int getDataField2() { return dataField2; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dataField1 == null) ? 0 : dataField1.hashCode()); result = prime * result + dataField2; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SimpleObject other = (SimpleObject) obj; if (dataField1 == null) { if (other.dataField1 != null) return false; } else if (!dataField1.equals(other.dataField1)) return false; if (dataField2 != other.dataField2) return false; return true; } } 

Die Karte enthält eindeutige Schlüssel. Wenn Sie put mit einem in der Map vorhandenen Schlüssel aufrufen, wird das Objekt unter diesem Schlüssel durch das neue Objekt ersetzt. Daher die Größe 1.

Der Unterschied zwischen den beiden sollte offensichtlich sein:

  • In einer Map speichern Sie Schlüssel-Wert-Paare
  • In einem Set speichern Sie nur die Schlüssel

Tatsächlich hat ein HashSet ein HashMap Feld, und wenn add(obj) aufgerufen wird, wird die put Methode auf der zugrundeliegenden Map map.put(obj, DUMMY) – wobei das Dummy-Objekt ein private static final Object DUMMY = new Object() . Die Karte enthält also Ihr Objekt als Schlüssel und einen Wert, der nicht von Interesse ist.

Ein Schlüssel in einer Map kann nur einem einzelnen Wert zugeordnet werden. Wenn Sie das zweite Mal mit demselben Schlüssel in die Karte einfügen, überschreibt es den ersten Eintrag.

Im Falle des HashSets ist das Hinzufügen desselben Objekts mehr oder weniger ein No-Op. Im Falle einer HashMap überschreibt das Einfügen eines neuen Schlüssels, Wertpaars mit einem vorhandenen Schlüssel den vorhandenen Wert, um einen neuen Wert für diesen Schlüssel festzulegen. Unten habe ich equals () checks zu deinem Code hinzugefügt:

 SimpleObject simpleObject1 = new SimpleObject("Igor", 1); SimpleObject simplObject2 = new SimpleObject("Igor", 1); //If the below prints true, the 2nd add will not add anything System.out.println("Are the objects equal? " , (simpleObject1.equals(simpleObject2)); testSet.add(simpleObject1); testSet.add(simplObject2); Integer key = new Integer(10); //This is a no-brainer as you've the exact same key, but lets keep it consistent //If this returns true, the 2nd put will overwrite the 1st key-value pair. testMap.put(key, simpleObject1); testMap.put(key, simplObject2); System.out.println("Are the keys equal? ", (key.equals(key)); System.out.println(testSet.size()); System.out.println(testMap.size()); 

Ich wollte nur diesen großartigen Antworten die Antwort auf dein letztes Dilemma hinzufügen. Sie wollten wissen, was der Unterschied zwischen diesen beiden Sammlungen ist, wenn sie nach dem Einfügen dieselbe Größe aufweisen. Nun, Sie können den Unterschied hier nicht wirklich sehen, weil Sie zwei Werte mit demselben Schlüssel in die Karte einfügen und somit den ersten Wert mit dem zweiten ändern. Sie würden den wirklichen Unterschied (unter den anderen) sehen, wenn Sie denselben Wert in die Karte eingefügt hätten, aber mit dem anderen Schlüssel . Dann würden Sie sehen, dass Sie doppelte Werte in der Karte haben können, aber Sie können keine doppelten Schlüssel haben , und in dem Satz können Sie keine doppelten Werte haben . Das ist der Hauptunterschied hier.

Die Antwort ist einfach, weil es sich um HashSets handelt. HashSet verwendet intern HashMap mit einem Dummy-Objekt namens PRESENT als Wert und KEY dieser hashmap wird Ihr Objekt sein.

Hash (SimpleObject1) und Hash (SimplObject2) geben denselben Int zurück. Damit?

Wenn Sie simpleObject1 zu hashset hinzufügen, wird dies in seine interne hashmap mit simpleObject1 als Schlüssel eingefügt. Wenn Sie dann (simplObject2) hinzufügen, erhalten Sie false, weil es in der internen hashmap bereits als Schlüssel verfügbar ist.

Als eine kleine zusätzliche Information verwendet HashSet eine effektive Hash-function, um O (1) -performance bereitzustellen, indem der objektgleiche () – und hashCode () – Vertrag verwendet wird. Aus diesem Grund erlaubt hashset nicht “null”, was nicht mit equals () und hashCode () in non-object implementiert werden kann.

Ich denke, der Hauptunterschied ist, HashSet ist stabil in dem Sinne, es ersetzt nicht doppelten Wert (wenn nach dem ersten eindeutigen Schlüssel gefunden, nur alle zukünftigen Duplikate vercasting), und HashMap wird sich bemühen, alte durch neue doppelte Wert zu ersetzen . Daher muss in HashMap ein Overhead vorhanden sein, um neue doppelte Objekte einzufügen.

public class HashSet extends AbstractSet implements Set, Cloneable, Serializable
Diese class implementiert die Set-Schnittstelle, die von einer Hash-Tabelle (eigentlich einer HashMap-Instanz) unterstützt wird. Es gibt keine Garantien hinsichtlich der Iterationsreihenfolge des Satzes; insbesondere garantiert es nicht, dass der Auftrag über die Zeit konstant bleibt. Diese class erlaubt das Null-Element.

Diese class bietet eine konstante Zeitleistung für die grundlegenden Operationen (hinzufügen, entfernen, enthalten und Größe), wenn man annimmt, dass die Hash-function die Elemente ordnungsgemäß unter den Buckets verteilt. Das Iterieren über diese Menge erfordert eine Zeit proportional zur Summe der Größe der HashSet-Instanz (die Anzahl der Elemente) plus der “Kapazität” der Backing-HashMap-Instanz (die Anzahl der Buckets). Daher ist es sehr wichtig, die Anfangskapazität nicht zu hoch einzustellen (oder den Lastfaktor zu niedrig), wenn die Iterationsleistung wichtig ist.

Beachten Sie, dass diese Implementierung nicht synchronisiert ist. Wenn mehrere Threads gleichzeitig auf einen Hash-Satz zugreifen und mindestens einer der Threads den Satz ändert, muss er extern synchronisiert werden. Dies wird typischerweise durch Synchronisieren mit einem Objekt erreicht, das die Menge natürlich kapselt. Wenn kein solches Objekt vorhanden ist, sollte die Menge mit der Methode Collections.synchronizedSet “umgebrochen” werden. Dies geschieht am besten zum Zeitpunkt der Erstellung, um einen versehentlichen unsynchronisierten Zugriff auf das Set More Details zu verhindern