Ein Handbuch zu HashSet in Java

Eine Anleitung zu HashSet in Java

1. Überblick

In diesem Artikel werden wir uns mitHashSet. befassen. Dies ist eine der beliebtestenSet-Implementierungen und ein integraler Bestandteil des Java Collections Framework.

2. Einführung inHashSet

HashSet ist eine der grundlegenden Datenstrukturen in der Java Collections API.

Erinnern wir uns an die wichtigsten Aspekte dieser Implementierung:

  • Es speichert eindeutige Elemente und lässt Nullen zu

  • Es wird durch einHashMap unterstützt

  • Die Einfügereihenfolge wird nicht beibehalten

  • Es ist nicht threadsicher

Beachten Sie, dass dieses interneHashMap initialisiert wird, wenn eine Instanz vonHashSet erstellt wird:

public HashSet() {
    map = new HashMap<>();
}

Wenn Sie näher auf die Funktionsweise vonHashMapeingehen möchten, können Siethe article focused on it herelesen.

3. Die API

In diesem Abschnitt werden wir die am häufigsten verwendeten Methoden überprüfen und einige einfache Beispiele betrachten.

3.1. add()

Dieadd()-Methode kann zum Hinzufügen von Elementen zu einer Menge verwendet werden. The method contract states that an element will be added only when it isn’t already present in a set. Wenn ein Element hinzugefügt wurde, gibt die Methodetrue, zurück, andernfalls -false.

Wir können einemHashSetein Element hinzufügen, wie:

@Test
public void whenAddingElement_shouldAddElement() {
    Set hashset = new HashSet<>();

    assertTrue(hashset.add("String Added"));
}

Aus Sicht der Implementierung ist die Methodeaddäußerst wichtig. Implementierungsdetails veranschaulichen, wieHashSet intern funktioniert und nutzen dieHashMap’sput-Methode:

public boolean add(E e) {
    return map.put(e, PRESENT) == null;
}

Die Variablemap ist eine Referenz auf die internen BackingHashMap:

private transient HashMap map;

Es ist eine gute Idee, sich zuerst mit denhashcodevertraut zu machen, um ein detailliertes Verständnis darüber zu erhalten, wie die Elemente in Hash-basierten Datenstrukturen organisiert sind.

Zusammenfassend:

  • EinHashMap ist ein Array vonbuckets mit einer Standardkapazität von 16 Elementen - jeder Bucket entspricht einem anderen Hashcode-Wert

  • Wenn verschiedene Objekte denselben Hashcode-Wert haben, werden sie in einem einzigen Bucket gespeichert

  • Wennload factor erreicht ist, wird ein neues Array erstellt, das doppelt so groß ist wie das vorherige, und alle Elemente werden erneut aufbereitet und auf neue entsprechende Buckets verteilt

  • Um einen Wert abzurufen, hashen wir einen Schlüssel, ändern ihn und gehen dann zu einem entsprechenden Bucket und durchsuchen die potenzielle verknüpfte Liste, falls es mehr als ein Objekt gibt

3.2. contains()

The purpose of the contains method is to check if an element is present in a given HashSet. Gibttrue zurück, wenn das Element gefunden wird, andernfallsfalse.

Wir können nach einem Element inHashSet suchen:

@Test
public void whenCheckingForElement_shouldSearchForElement() {
    Set hashsetContains = new HashSet<>();
    hashsetContains.add("String Added");

    assertTrue(hashsetContains.contains("String Added"));
}

Immer wenn ein Objekt an diese Methode übergeben wird, wird der Hashwert berechnet. Dann wird der entsprechende Bucket-Standort aufgelöst und durchlaufen.

3.3. remove()

Die Methode entfernt das angegebene Element aus der Menge, falls es vorhanden ist. Diese Methode gibttrue zurück, wenn eine Menge das angegebene Element enthält.

Sehen wir uns ein Arbeitsbeispiel an:

@Test
public void whenRemovingElement_shouldRemoveElement() {
    Set removeFromHashSet = new HashSet<>();
    removeFromHashSet.add("String Added");

    assertTrue(removeFromHashSet.remove("String Added"));
}

3.4. clear()

Wir verwenden diese Methode, wenn wir alle Elemente aus einem Satz entfernen möchten. Die zugrunde liegende Implementierung löscht einfach alle Elemente aus den zugrunde liegendenHashMap.

Lassen Sie uns das in Aktion sehen:

@Test
public void whenClearingHashSet_shouldClearHashSet() {
    Set clearHashSet = new HashSet<>();
    clearHashSet.add("String Added");
    clearHashSet.clear();

    assertTrue(clearHashSet.isEmpty());
}

3.5. size()

Dies ist eine der grundlegenden Methoden in der API. Es wird häufig verwendet, um die Anzahl der inHashSet vorhandenen Elemente zu ermitteln. Die zugrunde liegende Implementierung delegiert die Berechnung einfach an die MethodeHashMap’s size().

Lassen Sie uns das in Aktion sehen:

@Test
public void whenCheckingTheSizeOfHashSet_shouldReturnThesize() {
    Set hashSetSize = new HashSet<>();
    hashSetSize.add("String Added");

    assertEquals(1, hashSetSize.size());
}

3.6. isEmpty()

Mit dieser Methode können wir herausfinden, ob eine bestimmte Instanz vonHashSet leer ist oder nicht. Diese Methode gibttrue zurück, wenn die Menge keine Elemente enthält:

@Test
public void whenCheckingForEmptyHashSet_shouldCheckForEmpty() {
    Set emptyHashSet = new HashSet<>();

    assertTrue(emptyHashSet.isEmpty());
}

3.7. iterator()

Die Methode gibt einen Iterator über die Elemente inSet zurück. The elements are visited in no particular order and iterators are fail-fast.

Die zufällige Iterationsreihenfolge können wir hier beobachten:

@Test
public void whenIteratingHashSet_shouldIterateHashSet() {
    Set hashset = new HashSet<>();
    hashset.add("First");
    hashset.add("Second");
    hashset.add("Third");
    Iterator itr = hashset.iterator();
    while(itr.hasNext()){
        System.out.println(itr.next());
    }
}

Wenn die Menge zu irgendeinem Zeitpunkt nach dem Erstellen des Iterators geändert wird, außer durch die eigene Entfernungsmethode des Iterators, löstIteratorConcurrentModificationException aus.

Lassen Sie uns das in Aktion sehen:

@Test(expected = ConcurrentModificationException.class)
public void whenModifyingHashSetWhileIterating_shouldThrowException() {

    Set hashset = new HashSet<>();
    hashset.add("First");
    hashset.add("Second");
    hashset.add("Third");
    Iterator itr = hashset.iterator();
    while (itr.hasNext()) {
        itr.next();
        hashset.remove("Second");
    }
}

Wenn wir alternativ die Entfernungsmethode des Iterators verwendet hätten, wären wir nicht auf die Ausnahme gestoßen:

@Test
public void whenRemovingElementUsingIterator_shouldRemoveElement() {

    Set hashset = new HashSet<>();
    hashset.add("First");
    hashset.add("Second");
    hashset.add("Third");
    Iterator itr = hashset.iterator();
    while (itr.hasNext()) {
        String element = itr.next();
        if (element.equals("Second"))
            itr.remove();
    }

    assertEquals(2, hashset.size());
}

Das ausfallsichere Verhalten eines Iterators kann nicht garantiert werden, da es unmöglich ist, bei nicht synchronisierten gleichzeitigen Änderungen harte Garantien zu geben.

Fail-Fast-Iteratoren werfenConcurrentModificationException auf Best-Effort-Basis. Daher wäre es falsch, ein Programm zu schreiben, dessen Richtigkeit von dieser Ausnahme abhängt.

4. WieHashSet bewahrt die Eindeutigkeit?

Wenn wir ein Objekt inHashSet einfügen, verwendet es denhashcode-Wert des Objekts, um zu bestimmen, ob ein Element noch nicht in der Menge enthalten ist.

Jeder Hash-Code-Wert entspricht einer bestimmten Bucket-Position, die verschiedene Elemente enthalten kann, für die der berechnete Hash-Wert der gleiche ist. But two objects with the same hashCode might not be equal.

Daher werden Objekte innerhalb desselben Buckets mit der Methodeequals()verglichen.

5. Leistung vonHashSet

Die Leistung von aHashSet wird hauptsächlich von zwei Parametern beeinflusst -Initial Capacity undLoad Factor.

Die erwartete zeitliche Komplexität beim Hinzufügen eines Elements zu einer Menge beträgtO(1), die im schlimmsten Fall aufO(n) fallen kann (nur ein Bucket vorhanden) - daherit’s essential to maintain the right HashSet’s capacity.

Ein wichtiger Hinweis: Seit JDK 8the worst case time complexity is O(log*n).

Der Belastungsfaktor beschreibt den maximalen Füllstand, oberhalb dessen die Größe eines Sets geändert werden muss.

Wir können auchHashSet mit benutzerdefinierten Werten fürinitial capacity undload factor erstellen:

Set hashset = new HashSet<>();
Set hashset = new HashSet<>(20);
Set hashset = new HashSet<>(20, 0.5f);

Im ersten Fall werden die Standardwerte verwendet - die Anfangskapazität von 16 und der Lastfaktor von 0,75. Im zweiten Fall setzen wir die Standardkapazität außer Kraft und im dritten Fall setzen wir beide außer Kraft.

Eine geringe Anfangskapazität verringert die Raumkomplexität, erhöht jedoch die Häufigkeit des Wiederaufwärmens, was ein teurer Prozess ist.

Andererseits ista high initial capacity increases the cost of iteration and the initial memory consumption.

Als Faustregel gilt:

  • Eine hohe Anfangskapazität ist gut für eine große Anzahl von Einträgen bei geringer bis keiner Iteration

  • Eine niedrige Anfangskapazität ist gut für wenige Einträge mit viel Iteration

Es ist daher sehr wichtig, das richtige Gleichgewicht zwischen beiden zu finden. Normalerweise ist die Standardimplementierung optimiert und funktioniert einwandfrei. Sollten wir das Gefühl haben, diese Parameter an die Anforderungen anpassen zu müssen, müssen wir dies mit Bedacht tun.

6. Fazit

In diesem Artikel haben wir den Nutzen vonHashSet, seinen Zweck sowie die zugrunde liegende Arbeitsweise beschrieben. Wir haben gesehen, wie effizient es in Bezug auf die Benutzerfreundlichkeit ist, da es eine konstante Zeitleistung bietet und Doppelspurigkeiten vermeidet.

Wir haben einige der wichtigen Methoden der API untersucht, wie sie uns als Entwickler helfen können, einHashSet für sein Potenzial zu nutzen.

Wie immer können Codefragmenteover on GitHub gefunden werden.