Ein Guide zu TreeSet in Java

Eine Anleitung zu TreeSet in Java

1. Überblick

In diesem Artikel sehen wir uns einen integralen Bestandteil des Java Collections Framework undone of the most popular Set implementations – the TreeSet an.

2. Einführung inTreeSet

Einfach ausgedrückt istTreeSet eine sortierte Sammlung, die die KlasseAbstractSeterweitert und die SchnittstelleNavigableSetimplementiert.

Hier ist eine kurze Zusammenfassung der wichtigsten Aspekte dieser Implementierung:

  • Es speichert einzigartige Elemente

  • Die Einfügereihenfolge der Elemente wird nicht beibehalten

  • Es sortiert die Elemente in aufsteigender Reihenfolge

  • Es ist nicht threadsicher

In this implementation, objects are sorted and stored in ascending order according to their natural order. DasTreeSet verwendet einen selbstausgleichenden binären Suchbaum, genauer gesagta Red-Black tree.

Einfach ausgedrückt, als selbstausgleichender binärer Suchbaum besteht jeder Knoten des binären Baums aus einem zusätzlichen Bit, mit dem die Farbe des Knotens identifiziert wird, der entweder rot oder schwarz ist. Bei nachfolgenden Einfügungen und Löschungen tragen diese "Farb" -Bits dazu bei, dass der Baum mehr oder weniger ausgeglichen bleibt.

Erstellen wir also eine Instanz vonTreeSet:

Set treeSet = new TreeSet<>();

2.1. TreeSet mit einem Konstruktor-Komparator Param

Optional können wir einTreeSet mit einem Konstruktor konstruieren, mit dem wir die Reihenfolge definieren können, in der die Elemente mithilfe vonComparable oderComparator: sortiert werden

Set treeSet = new TreeSet<>(Comparator.comparing(String::length));

ObwohlTreeSet nicht threadsicher ist, kann es extern mit dem WrapperCollections.synchronizedSet() synchronisiert werden:

Set syncTreeSet = Collections.synchronizedSet(treeSet);

Okay, jetzt, da wir eine klare Vorstellung davon haben, wie eineTreeSet-Instanz erstellt wird, werfen wir einen Blick auf die allgemeinen verfügbaren Operationen.

3. TreeSet  hinzufügen()

Die Methodeadd() kann erwartungsgemäß zum Hinzufügen von Elementen zuTreeSet verwendet werden. Wenn ein Element hinzugefügt wurde, gibt die Methodetrue, zurück, andernfalls -false.

Der Vertrag der Methode besagt, dass ein Element nur hinzugefügt wird, wenn es nicht bereits inSet vorhanden ist.

Fügen wir einemTreeSet ein Element hinzu:

@Test
public void whenAddingElement_shouldAddElement() {
    Set treeSet = new TreeSet<>();

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

The add method is extremely important as the implementation details of the method illustrate how the TreeSet works internally, wie es dieTreeMap’sput-Methode nutzt, um die Elemente zu speichern:

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

Die Variablem bezieht sich auf einen internen HintergrundTreeMap (beachten Sie, dassTreeMapNavigateableMap implementiert):

private transient NavigableMap m;

Daher hängtTreeSet intern von einem HintergrundNavigableMap ab, der mit einer Instanz vonTreeMap initialisiert wird, wenn eine Instanz vonTreeSet erstellt wird:

public TreeSet() {
    this(new TreeMap());
}

Mehr dazu finden Sie inthis article.

4. TreeSet contains()

The contains() method is used to check if a given element is present in a given TreeSet. Wenn das Element gefunden wird, gibt es true zurück, andernfallsfalse.

Lassen Sie uns diecontains() in Aktion sehen:

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

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

5 TreeSet remove()

Dieremove()-Methode wird verwendet, um das angegebene Element aus der Menge zu entfernen, falls es vorhanden ist.

Wenn eine Menge das angegebene Element enthielt, gibt diese Methodetrue. zurück

Lassen Sie es uns in Aktion sehen:

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

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

6. TreeSet clear()

Wenn wir alle Elemente aus einer Menge entfernen möchten, können wir die Methodeclear()verwenden:

@Test
public void whenClearingTreeSet_shouldClearTreeSet() {
    Set clearTreeSet = new TreeSet<>();
    clearTreeSet.add("String Added");
    clearTreeSet.clear();

    assertTrue(clearTreeSet.isEmpty());
}

7. TreeSet size()

Diesize()-Methode wird verwendet, um die Anzahl der inTreeSet vorhandenen Elemente zu identifizieren. Dies ist eine der grundlegenden Methoden in der API:

@Test
public void whenCheckingTheSizeOfTreeSet_shouldReturnThesize() {
    Set treeSetSize = new TreeSet<>();
    treeSetSize.add("String Added");

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

8. TreeSet isEmpty ()

Die MethodeisEmpty() kann verwendet werden, um herauszufinden, ob eine bestimmte Instanz vonTreeSetleer ist oder nicht:

@Test
public void whenCheckingForEmptyTreeSet_shouldCheckForEmpty() {
    Set emptyTreeSet = new TreeSet<>();

    assertTrue(emptyTreeSet.isEmpty());
}

9. TreeSet iterator()

Die Methodeiterator() gibt einen Iterator zurück, der in aufsteigender Reihenfolge über die Elemente inSet.Those iterators are fail-fast iteriert.

Die aufsteigende Reihenfolge der Iterationen können wir hier beobachten:

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

Zusätzlich können wir mitTreeSet dieSet in absteigender Reihenfolge durchlaufen.

Lassen Sie uns das in Aktion sehen:

@Test
public void whenIteratingTreeSet_shouldIterateTreeSetInDescendingOrder() {
    TreeSet treeSet = new TreeSet<>();
    treeSet.add("First");
    treeSet.add("Second");
    treeSet.add("Third");
    Iterator itr = treeSet.descendingIterator();
    while (itr.hasNext()) {
        System.out.println(itr.next());
    }
}

Iterator löst einConcurrentModificationException iaus, wenn die Menge jederzeit geändert wird, nachdem der Iterator auf irgendeine Weise erstellt wurde, außer durch dieremove()-Methode des Iterators.

Erstellen wir einen Test dafür:

@Test(expected = ConcurrentModificationException.class)
public void whenModifyingTreeSetWhileIterating_shouldThrowException() {
    Set treeSet = new TreeSet<>();
    treeSet.add("First");
    treeSet.add("Second");
    treeSet.add("Third");
    Iterator itr = treeSet.iterator();
    while (itr.hasNext()) {
        itr.next();
        treeSet.remove("Second");
    }
}

Wenn wir alternativ die Methode zum Entfernen des Iterators verwendet hätten, wäre die Ausnahme nicht aufgetreten:

@Test
public void whenRemovingElementUsingIterator_shouldRemoveElement() {

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

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

Es gibt keine Garantie für das ausfallsichere Verhalten eines Iterators, da es unmöglich ist, bei nicht synchronisierten gleichzeitigen Änderungen harte Garantien zu geben.

Mehr dazu finden Sie inhere.

10. TreeSet first()

Diese Methode gibt das erste Element vonTreeSet zurück, wenn es nicht leer ist. Andernfalls wird einNoSuchElementException ausgelöst.

Sehen wir uns ein Beispiel an:

@Test
public void whenCheckingFirstElement_shouldReturnFirstElement() {
    TreeSet treeSet = new TreeSet<>();
    treeSet.add("First");

    assertEquals("First", treeSet.first());
}

11. TreeSet last()

Analog zum obigen Beispiel gibt diese Methode das letzte Element zurück, wenn die Menge nicht leer ist:

@Test
public void whenCheckingLastElement_shouldReturnLastElement() {
    TreeSet treeSet = new TreeSet<>();
    treeSet.add("First");
    treeSet.add("Last");

    assertEquals("Last", treeSet.last());
}

12. TreeSet subSet()

Diese Methode gibt die Elemente zurück, die vonfromElement bistoElement. reichen. Beachten Sie, dassfromElement inklusive undtoElement exklusiv ist:

@Test
public void whenUsingSubSet_shouldReturnSubSetElements() {
    SortedSet treeSet = new TreeSet<>();
    treeSet.add(1);
    treeSet.add(2);
    treeSet.add(3);
    treeSet.add(4);
    treeSet.add(5);
    treeSet.add(6);

    Set expectedSet = new TreeSet<>();
    expectedSet.add(2);
    expectedSet.add(3);
    expectedSet.add(4);
    expectedSet.add(5);

    Set subSet = treeSet.subSet(2, 6);

    assertEquals(expectedSet, subSet);
}

13. TreeSet headSet()

Diese Methode gibt Elemente vonTreeSet zurück, die kleiner als das angegebene Element sind:

@Test
public void whenUsingHeadSet_shouldReturnHeadSetElements() {
    SortedSet treeSet = new TreeSet<>();
    treeSet.add(1);
    treeSet.add(2);
    treeSet.add(3);
    treeSet.add(4);
    treeSet.add(5);
    treeSet.add(6);

    Set subSet = treeSet.headSet(6);

    assertEquals(subSet, treeSet.subSet(1, 6));
}

14. TreeSet tailSet()

Diese Methode gibt die Elemente vonTreeSet zurück, die größer oder gleich dem angegebenen Element sind:

@Test
public void whenUsingTailSet_shouldReturnTailSetElements() {
    NavigableSet treeSet = new TreeSet<>();
    treeSet.add(1);
    treeSet.add(2);
    treeSet.add(3);
    treeSet.add(4);
    treeSet.add(5);
    treeSet.add(6);

    Set subSet = treeSet.tailSet(3);

    assertEquals(subSet, treeSet.subSet(3, true, 6, true));
}

15. Speichern vonNull Elementen

Vor Java 7 war es möglich,null Elemente zu einem leeren Element hinzuzufügen TreeSet.

Dies wurde jedoch als Fehler angesehen. Daher istTreeSet no longer supports the addition of null.

Wenn wir denTreeSet, Elemente hinzufügen, werden die Elemente nach ihrer natürlichen Reihenfolge oder gemäß dencomparator. sortiert. Wenn Sie also im Vergleich zu vorhandenen Elementen einnull, hinzufügen, erhalten Sie einNullPointerException) s danull mit keinem Wert verglichen werden kann:

@Test(expected = NullPointerException.class)
public void whenAddingNullToNonEmptyTreeSet_shouldThrowException() {
    Set treeSet = new TreeSet<>();
    treeSet.add("First");
    treeSet.add(null);
}

InTreeSet eingefügte Elemente müssen entweder dieComparable-Schnittstelle implementieren oder zumindest vom angegebenen Komparator akzeptiert werden. All such elements must be mutually comparable,i.e.e1.compareTo(e2) or comparator.compare(e1, e2)mustn’t throw a ClassCastException.

Sehen wir uns ein Beispiel an:

class Element {
    private Integer id;

    // Other methods...
}

Comparator comparator = (ele1, ele2) -> {
    return ele1.getId().compareTo(ele2.getId());
};

@Test
public void whenUsingComparator_shouldSortAndInsertElements() {
    Set treeSet = new TreeSet<>(comparator);
    Element ele1 = new Element();
    ele1.setId(100);
    Element ele2 = new Element();
    ele2.setId(200);

    treeSet.add(ele1);
    treeSet.add(ele2);

    System.out.println(treeSet);
}

16. Leistung vonTreeSet

Im Vergleich zu aHashSet liegt die Leistung von aTreeSet auf der unteren Seite. Operationen wieadd,remove undsearch benötigenO(log n) Zeit, während Operationen wie das Drucken vonn Elementen in sortierter ReihenfolgeO(n) Zeit benötigen.

EinTreeSet sollte unsere primäre Wahl sein, wenn wir unsere Einträge sortieren möchten, da aufTreeSet in aufsteigender oder absteigender Reihenfolge zugegriffen und diese durchlaufen werden können und die Leistung aufsteigender Operationen und Ansichten wahrscheinlich ist schneller als die von absteigenden.

Das Prinzip der Lokalität - ist ein Begriff für das Phänomen, bei dem abhängig vom Speicherzugriffsmuster häufig auf dieselben Werte oder verwandte Speicherorte zugegriffen wird.

Wenn wir Lokalität sagen:

  • Auf ähnliche Daten wird häufig von einer Anwendung mit ähnlicher Häufigkeit zugegriffen

  • Wenn zwei Einträge bei einer bestimmten Reihenfolge in der Nähe sind, platziert einTreeSet sie in der Datenstruktur und damit im Speicher nahe beieinander

Da ATreeSet eine Datenstruktur mit größerer Lokalität ist, können wir daher gemäß dem Prinzip der Lokalität schließen, dass wirTreeSet den Vorzug geben sollten, wenn wir wenig Speicher haben und wenn wir auf Elemente zugreifen möchten, die entsprechend ihrer natürlichen Reihenfolge relativ nahe beieinander liegen.

Wenn Daten von der Festplatte gelesen werden müssen (die eine höhere Latenz aufweist als Daten, die aus dem Cache oder Speicher gelesen werden), bevorzugen SieTreeSet, da sie eine größere Lokalität aufweisen

17. Fazit

In diesem Artikel konzentrieren wir uns auf das Verständnis der Verwendung der Standardimplementierung vonTreeSetin Java. Wir haben gesehen, wozu es dient und wie effizient es in Bezug auf die Benutzerfreundlichkeit ist, da es Doppelungen und Sortierelemente vermeidet.

Wie immer können Codefragmenteover on GitHub gefunden werden.