Eine Anleitung zu TreeMap in Java

Eine Anleitung zu TreeMap in Java

1. Überblick

In diesem Artikel werden wir die Implementierung derMap-Schnittstelle vonTreeMapaus dem Java Collections Framework (JCF) untersuchen.

TreeMap ist eine Kartenimplementierung, die ihre Einträge nach der natürlichen Reihenfolge ihrer Schlüssel sortiert oder besser noch einen Komparator verwendet, wenn sie vom Benutzer zur Erstellungszeit bereitgestellt werden.

Zuvor haben wir die Implementierungen vonHashMap undLinkedHashMapbehandelt, und wir werden feststellen, dass es einige Informationen darüber gibt, wie diese Klassen funktionieren, die ähnlich sind.

Es wird dringend empfohlen, die genannten Artikel zu lesen, bevor Sie mit diesem fortfahren.

2. Standardsortierung inTreeMap

Standardmäßig sortiertTreeMap alle Einträge nach ihrer natürlichen Reihenfolge. Für eine ganze Zahl bedeutet dies aufsteigende Reihenfolge und für Zeichenfolgen alphabetische Reihenfolge.

Sehen wir uns die natürliche Reihenfolge in einem Test an:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect() {
    TreeMap map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    assertEquals("[1, 2, 3, 4, 5]", map.keySet().toString());
}

Beachten Sie, dass wir die ganzzahligen Schlüssel nicht in der richtigen Reihenfolge platziert haben, sondern beim Abrufen des Schlüsselsatzes bestätigen, dass sie in der Tat in aufsteigender Reihenfolge beibehalten werden. Dies ist die natürliche Reihenfolge von ganzen Zahlen.

Wenn wir Zeichenfolgen verwenden, werden diese ebenfalls in ihrer natürlichen Reihenfolge sortiert, d. H. alphabetisch:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect2() {
    TreeMap map = new TreeMap<>();
    map.put("c", "val");
    map.put("b", "val");
    map.put("a", "val");
    map.put("e", "val");
    map.put("d", "val");

    assertEquals("[a, b, c, d, e]", map.keySet().toString());
}

TreeMap verwendet im Gegensatz zu einer Hash-Map und einer verknüpften Hash-Map nirgendwo das Hashing-Prinzip, da es kein Array zum Speichern seiner Einträge verwendet.

3. Benutzerdefinierte Sortierung inTreeMap

Wenn wir mit der natürlichen Reihenfolge vonTreeMap nicht zufrieden sind, können wir auch unsere eigene Regel für die Reihenfolge mithilfe eines Komparators während der Erstellung einer Baumkarte definieren.

Im folgenden Beispiel sollen die Ganzzahlschlüssel in absteigender Reihenfolge sortiert werden:

@Test
public void givenTreeMap_whenOrdersEntriesByComparator_thenCorrect() {
    TreeMap map =
      new TreeMap<>(Comparator.reverseOrder());
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    assertEquals("[5, 4, 3, 2, 1]", map.keySet().toString());
}

Eine Hash-Karte garantiert nicht die Reihenfolge der gespeicherten Schlüssel und garantiert insbesondere nicht, dass diese Reihenfolge im Laufe der Zeit gleich bleibt. Eine Baumkarte garantiert jedoch, dass die Schlüssel immer nach der angegebenen Reihenfolge sortiert werden.

4. Bedeutung vonTreeMap Sortierung

Wir wissen jetzt, dassTreeMap alle seine Einträge in sortierter Reihenfolge speichert. Aufgrund dieses Attributs von Baumkarten können wir Abfragen ausführen wie: finde "größte", finde "kleinste", finde alle Schlüssel, die kleiner oder größer als ein bestimmter Wert sind, usw.

Der folgende Code deckt nur einen kleinen Prozentsatz dieser Fälle ab:

@Test
public void givenTreeMap_whenPerformsQueries_thenCorrect() {
    TreeMap map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    Integer highestKey = map.lastKey();
    Integer lowestKey = map.firstKey();
    Set keysLessThan3 = map.headMap(3).keySet();
    Set keysGreaterThanEqTo3 = map.tailMap(3).keySet();

    assertEquals(new Integer(5), highestKey);
    assertEquals(new Integer(1), lowestKey);
    assertEquals("[1, 2]", keysLessThan3.toString());
    assertEquals("[3, 4, 5]", keysGreaterThanEqTo3.toString());
}

5. Interne Implementierung vonTreeMap

TreeMap implementiert die Schnittstelle vonNavigableMapund basiert die interne Arbeit auf den Prinzipien von rot-schwarzen Bäumen:

public class TreeMap extends AbstractMap
  implements NavigableMap, Cloneable, java.io.Serializable

Das Prinzip der rot-schwarzen Bäume geht über den Rahmen dieses Artikels hinaus. Es sind jedoch wichtige Dinge zu beachten, um zu verstehen, wie sie inTreeMap passen.

First of all, ein rot-schwarzer Baum ist eine Datenstruktur, die aus Knoten besteht; Stellen Sie sich einen umgekehrten Mangobaum vor, dessen Wurzel am Himmel liegt und dessen Zweige nach unten wachsen. Die Wurzel enthält das erste Element, das dem Baum hinzugefügt wurde.

Die Regel lautet, dass ausgehend von der Wurzel jedes Element im linken Zweig eines Knotens immer kleiner als das Element im Knoten selbst ist. Die rechts sind immer größer. Was mehr oder weniger definiert, wird durch die natürliche Reihenfolge der Elemente oder den definierten Komparator beim Bauen bestimmt, wie wir zuvor gesehen haben.

Diese Regel garantiert, dass die Einträge einer Baumkarte immer in sortierter und vorhersehbarer Reihenfolge vorliegen.

Secondly, ein rot-schwarzer Baum ist ein selbstausgleichender binärer Suchbaum. Dieses Attribut und das oben Gesagte garantieren, dass grundlegende Operationen wie Suchen, Abrufen, Setzen und Entfernen logarithmische ZeitO(log n) benötigen.

Selbstausgleich ist hier der Schlüssel. Stellen Sie sich vor, wie der Baum an einer Kante länger oder an der anderen kürzer wird, während wir ständig Einträge einfügen und löschen.

Dies würde bedeuten, dass eine Operation für den kürzeren Zweig eine kürzere Zeit und für den Zweig, der am weitesten von der Wurzel entfernt ist, eine längere Zeit benötigt, was wir nicht möchten.

Daher wird dies bei der Gestaltung von rot-schwarzen Bäumen berücksichtigt. Für jedes Einfügen und Löschen wird die maximale Höhe des Baums an einer beliebigen Kante beiO(log n) gehalten, d.h. Der Baum gleicht sich kontinuierlich aus.

Genau wie eine Hash-Map und eine verknüpfte Hash-Map wird eine Baum-Map nicht synchronisiert, und daher sind die Regeln für die Verwendung in einer Multithread-Umgebung ähnlich denen in den beiden anderen Map-Implementierungen.

6. Auswahl der richtigen Karte

Nachdem wir uns die Implementierungen vonHashMap undLinkedHashMap zuvor und jetztTreeMap angesehen haben, ist es wichtig, einen kurzen Vergleich zwischen den drei durchzuführen, um uns zu zeigen, welche wo passt.

A hash map eignet sich als allgemeine Kartenimplementierung, die schnelle Speicher- und Abrufvorgänge ermöglicht. Es fällt jedoch aufgrund seiner chaotischen und ungeordneten Anordnung der Einträge zu kurz.

Dies führt zu einer schlechten Leistung in Szenarien mit vielen Iterationen, da die gesamte Kapazität des zugrunde liegenden Arrays sich nicht nur auf die Anzahl der Einträge auswirkt.

A linked hash map besitzt die guten Eigenschaften von Hash-Maps und fügt den Einträgen Ordnung hinzu. Bei vielen Iterationen ist die Leistung besser, da unabhängig von der Kapazität nur die Anzahl der Einträge berücksichtigt wird.

A tree map bringt die Bestellung auf die nächste Ebene, indem es die vollständige Kontrolle darüber gibt, wie die Schlüssel sortiert werden sollen. Auf der anderen Seite bietet es eine schlechtere allgemeine Leistung als die beiden anderen Alternativen.

Wir könnten einlinked hash map reduces the chaos in the ordering of a hash map without incurring the performance penalty of a tree map sagen.

7. Fazit

In diesem Artikel haben wir die Klasse von JavaTreeMapund ihre interne Implementierung untersucht. Da es sich um die letzte einer Reihe gängiger Implementierungen von Kartenschnittstellen handelt, haben wir auch kurz erläutert, wo sie im Verhältnis zu den beiden anderen am besten passen.

Den vollständigen Quellcode für alle in diesem Artikel verwendeten Beispiele finden Sie inGitHub project.