Eine Anleitung zu TreeMap in Java

1. Übersicht

In diesem Artikel werden wir die TreeMap- Implementierung der Map- Schnittstelle von 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.

Bisher haben wir abgedeckt HashMap und LinkedHashMap Implementierungen und wir werden erkennen , dass es ziemlich viel Informationen darüber , wie diese Klassen arbeiten , die ähnlich ist.

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

2. Standardsortierung in TreeMap

Standardmäßig sortiert TreeMap alle Einträge nach ihrer natürlichen Reihenfolge. Für eine Ganzzahl würde dies eine aufsteigende Reihenfolge und für Zeichenfolgen eine alphabetische Reihenfolge bedeuten.

Lassen Sie uns die natürliche Reihenfolge in einem Test sehen:

@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 Ganzzahlschlüssel nicht ordnungsgemäß angeordnet haben. Beim Abrufen des Schlüsselsatzes bestätigen wir jedoch, dass sie tatsächlich 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, dh 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 in TreeMap

Wenn wir mit der natürlichen Reihenfolge von TreeMap 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 möchten wir, dass die Ganzzahlschlüssel in absteigender Reihenfolge angeordnet 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-Map garantiert nicht die Reihenfolge der gespeicherten Schlüssel und insbesondere nicht, dass diese Reihenfolge über die Zeit gleich bleibt. Eine Baumkarte garantiert jedoch, dass die Schlüssel immer nach der angegebenen Reihenfolge sortiert werden.

4. Bedeutung der TreeMap- Sortierung

Wir wissen jetzt, dass TreeMap alle Einträge in sortierter Reihenfolge speichert. Aufgrund dieses Attributs von Baumkarten können wir Abfragen ausführen wie: Finden Sie "größte", finden Sie "kleinste", finden Sie alle Schlüssel kleiner oder größer als ein bestimmter Wert 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 von TreeMap

TreeMap implementiert die NavigableMap- Schnittstelle und basiert seine 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 in TreeMap passen .

Zu allererst wird ein Rot-Schwarz-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 ist als das Element im Knoten selbst. Die rechts sind immer größer. Was mehr oder weniger definiert als, wird durch die natürliche Reihenfolge der Elemente oder den definierten Komparator bei der Konstruktion bestimmt, wie wir zuvor gesehen haben.

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

Zweitens ist ein rot-schwarzer Baum ein selbstausgleichender binärer Suchbaum. Dieses Attribut und die obigen Angaben garantieren, dass grundlegende Operationen wie Suchen, Abrufen, Setzen und Entfernen die logarithmische Zeit O (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 Einträge einfügen und löschen.

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

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

Genau wie die Hash-Map und die verknüpfte Hash-Map ist eine Tree-Map nicht synchronisiert. Daher ähneln die Regeln für die Verwendung in einer Multithread-Umgebung denen in den beiden anderen Map-Implementierungen.

6. Auswahl der richtigen Karte

Nachdem betrachtet HashMap und LinkedHashMap Implementierungen früher und jetzt TreeMap ist es wichtig , einen kurzen Vergleich zwischen den drei zu machen , die uns zu leiten , auf dem man passt , wo.

Eine Hash-Map eignet sich gut als Allzweck-Map-Implementierung, 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 dazu, dass die Leistung in Szenarien mit vielen Iterationen schlecht ist, da die gesamte Kapazität des zugrunde liegenden Arrays nicht nur die Anzahl der Einträge, sondern auch die Durchquerung beeinflusst.

Eine verknüpfte Hash-Map besitzt die guten Eigenschaften von Hash-Maps und fügt den Einträgen Ordnung hinzu. Bei viel Iteration ist die Leistung besser, da unabhängig von der Kapazität nur die Anzahl der Einträge berücksichtigt wird.

Eine Baumkarte bringt die Reihenfolge auf die nächste Ebene, indem sie 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 sagen, dass eine verknüpfte Hash-Karte das Chaos bei der Reihenfolge einer Hash-Karte verringert, ohne dass die Leistungseinbußen einer Baumkarte anfallen .

7. Fazit

In diesem Artikel haben wir die Java TreeMap- Klasse und ihre interne Implementierung untersucht. Da es das letzte einer Reihe gängiger Map-Interface-Implementierungen ist, haben wir auch kurz besprochen, wo es im Verhältnis zu den beiden anderen am besten passt.

Der vollständige Quellcode für alle in diesem Artikel verwendeten Beispiele befindet sich im GitHub-Projekt.