Ein Handbuch zu LinkedHashMap in Java

Eine Anleitung zu LinkedHashMap in Java

1. Überblick

In diesem Artikel werden wir die interne Implementierung derLinkedHashMap-Klasse untersuchen. LinkedHashMap ist eine übliche Implementierung derMap-Schnittstelle.

Diese spezielle Implementierung ist eine Unterklasse vonHashMap und teilt daher die Kernbausteine ​​vonHashMap implementation. Aus diesem Grund wird dringend empfohlen, dies zu aktualisieren, bevor Sie mit diesem Artikel fortfahren.

2. LinkedHashMap vsHashMap

Die KlasseLinkedHashMap ist in den meisten AspektenHashMap sehr ähnlich. Die verknüpfte Hash-Map basiert jedoch sowohl auf der Hash-Tabelle als auch auf der verknüpften Liste, um die Funktionalität der Hash-Map zu verbessern.

Zusätzlich zu einem zugrunde liegenden Array der Standardgröße 16 verwaltet es eine doppelt verknüpfte Liste, die alle Einträge durchläuft.

Um die Reihenfolge der Elemente beizubehalten, ändert die verknüpfte Hashmap dieMap.Entry-Klasse vonHashMap, indem Zeiger auf den nächsten und vorherigen Eintrag hinzugefügt werden:

static class Entry extends HashMap.Node {
    Entry before, after;
    Entry(int hash, K key, V value, Node next) {
        super(hash, key, value, next);
    }
}

Beachten Sie, dass die KlasseEntryeinfach zwei Zeiger hinzufügt. before undafter, die es ihm ermöglichen, sich an die verknüpfte Liste zu binden. Abgesehen davon wird die Klassenimplementierung vonEntryeiner HashMap verwendet.

Denken Sie schließlich daran, dass diese verknüpfte Liste die Reihenfolge der Iteration definiert, die standardmäßig die Reihenfolge der Einfügung von Elementen ist (Einfügungsreihenfolge).

3. EinfügereihenfolgeLinkedHashMap

Schauen wir uns eine verknüpfte Hash-Map-Instanz an, die ihre Einträge nach ihrer Einfügung in die Map sortiert. Dies garantiert auch, dass diese Reihenfolge während des gesamten Lebenszyklus der Karte beibehalten wird:

@Test
public void givenLinkedHashMap_whenGetsOrderedKeyset_thenCorrect() {
    LinkedHashMap map = new LinkedHashMap<>();
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);

    Set keys = map.keySet();
    Integer[] arr = keys.toArray(new Integer[0]);

    for (int i = 0; i < arr.length; i++) {
        assertEquals(new Integer(i + 1), arr[i]);
    }
}

Hier führen wir lediglich einen rudimentären, nicht abschließenden Test zur Reihenfolge der Einträge in der verknüpften Hash-Karte durch.

Wir können garantieren, dass dieser Test immer bestanden wird, da die Einfügereihenfolge immer beibehalten wird. We cannot make the same guarantee for a HashMap.

Dieses Attribut kann in einer API von großem Vorteil sein, die eine beliebige Map empfängt, eine zu manipulierende Kopie erstellt und an den aufrufenden Code zurückgibt. Wenn der Client die zurückgegebene Karte vor dem Aufrufen der API auf dieselbe Weise bestellen möchte, ist eine verknüpfte Hashmap der richtige Weg.

Die Einfügereihenfolge wird nicht beeinflusst, wenn ein Schlüssel erneut in die Karte eingefügt wird.

4. ZugriffsreihenfolgeLinkedHashMap

LinkedHashMap bietet einen speziellen Konstruktor, mit dem wir unter dem benutzerdefinierten Lastfaktor (LF) und der Anfangskapazitäta different ordering mechanism/strategy called access-order angeben können:

LinkedHashMap map = new LinkedHashMap<>(16, .75f, true);

Der erste Parameter ist die Anfangskapazität, gefolgt vom Lastfaktor undlast param is the ordering mode. Durch Übergabe vontrue haben wir die Zugriffsreihenfolge ermittelt, während die Standardeinstellung die Einfügereihenfolge war.

Dieser Mechanismus stellt sicher, dass die Reihenfolge der Iteration von Elementen die Reihenfolge ist, in der zuletzt auf die Elemente zugegriffen wurde, vom letzten Zugriff bis zum letzten Zugriff.

Daher ist das Erstellen eines LRU-Caches (Least Recent Used) mit einer Art Karte recht einfach und praktisch. Eine erfolgreiche Operationput oderget führt zu einem Zugriff auf den Eintrag:

@Test
public void givenLinkedHashMap_whenAccessOrderWorks_thenCorrect() {
    LinkedHashMap map
      = new LinkedHashMap<>(16, .75f, true);
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);

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

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

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

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

Beachten Sie, wiethe order of elements in the key set is transformed as we perform access operations on the map.

Einfach ausgedrückt führt jede Zugriffsoperation auf die Karte zu einer Reihenfolge, in der das Element, auf das zugegriffen wurde, als letztes angezeigt wird, wenn eine Iteration sofort ausgeführt würde.

Nach den obigen Beispielen sollte es offensichtlich sein, dass eineputAll-Operation einen Eintragszugriff für jede der Zuordnungen in der angegebenen Zuordnung erzeugt.

Die Iteration über eine Ansicht der Karte hat natürlich keinen Einfluss auf die Reihenfolge der Iteration der Hintergrundkarte. only explicit access operations on the map will affect the order.

LinkedHashMap bietet auch einen Mechanismus zum Beibehalten einer festen Anzahl von Zuordnungen und zum Löschen der ältesten Einträge, falls neue hinzugefügt werden müssen.

Die MethoderemoveEldestEntrykann überschrieben werden, um diese Richtlinie zum automatischen Entfernen veralteter Zuordnungen durchzusetzen.

Um dies in der Praxis zu sehen, erstellen wir unsere eigene verknüpfte Hash-Map-Klasse, um das Entfernen veralteter Zuordnungen durch Erweitern vonLinkedHashMap zu erzwingen:

public class MyLinkedHashMap extends LinkedHashMap {

    private static final int MAX_ENTRIES = 5;

    public MyLinkedHashMap(
      int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

}

Mit dem obigen Override kann die Map auf eine maximale Größe von 5 Einträgen vergrößert werden. Wenn die Größe diese überschreitet, wird jeder neue Eintrag auf Kosten des Verlusts des ältesten Eintrags in der Karte eingefügt, d. H. der Eintrag, dessen letzte Zugriffszeit vor allen anderen Einträgen liegt:

@Test
public void givenLinkedHashMap_whenRemovesEldestEntry_thenCorrect() {
    LinkedHashMap map
      = new MyLinkedHashMap<>(16, .75f, true);
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);
    Set keys = map.keySet();
    assertEquals("[1, 2, 3, 4, 5]", keys.toString());

    map.put(6, null);
    assertEquals("[2, 3, 4, 5, 6]", keys.toString());

    map.put(7, null);
    assertEquals("[3, 4, 5, 6, 7]", keys.toString());

    map.put(8, null);
    assertEquals("[4, 5, 6, 7, 8]", keys.toString());
}

Beachten Sie, wie die ältesten Einträge am Anfang des Schlüsselsatzes immer wieder abfallen, wenn wir der Karte neue hinzufügen.

5. Leistungsüberlegungen

Genau wieHashMap führtLinkedHashMap die grundlegendenMap-Operationen zum Hinzufügen, Entfernen und Enthalten in konstanter Zeit aus, solange die Hash-Funktion gut dimensioniert ist. Es akzeptiert auch einen Nullschlüssel sowie Nullwerte.

Dieser Prozentsatz (t0) ist jedoch auf den zusätzlichen Aufwand für die Pflege einer doppelt verknüpften Liste zurückzuführen.

Die Iteration über Sammlungsansichten vonLinkedHashMap benötigt ebenfalls eine lineare ZeitO(n), ähnlich der vonHashMap. Auf der anderen SeiteLinkedHashMap‘s linear time performance during iteration is better than HashMap‘s linear time.

Dies liegt daran, dass fürLinkedHashMapn inO(n) nur die Anzahl der Einträge in der Karte ist, unabhängig von der Kapazität. Während fürHashMapn die Kapazität und die GrößeO(size+capacity). ist

Lastfaktor und Anfangskapazität sind genau wie fürHashMap definiert. Beachten Sie jedoch, dassthe penalty for choosing an excessively high value for initial capacity is less severe for LinkedHashMap than for HashMap als Iterationszeiten für diese Klasse von der Kapazität nicht beeinflusst werden.

6. Parallelität

Genau wie beiHashMap ist die Implementierung vonLinkedHashMapnicht synchronisiert. Wenn Sie also von mehreren Threads aus darauf zugreifen und mindestens einer dieser Threads ihn wahrscheinlich strukturell ändert, muss er extern synchronisiert werden.

Am besten tun Sie dies bei der Erstellung:

Map m = Collections.synchronizedMap(new LinkedHashMap());

Der Unterschied zuHashMap liegt in einer strukturellen Modifikation. In access-ordered linked hash maps, merely calling the get API results in a structural modification. Daneben gibt es Operationen wieput undremove.

7. Fazit

In diesem Artikel haben wir die JavaLinkedHashMap-Klasse als eine der wichtigsten Implementierungen derMap-Schnittstelle in Bezug auf die Verwendung untersucht. Wir haben auch seine internen Abläufe im Hinblick auf den Unterschied zuHashMap untersucht, der seine Oberklasse darstellt.

Nach dem Lesen dieses Beitrags können Sie hoffentlich fundiertere und effektivere Entscheidungen darüber treffen, welche Map-Implementierung in Ihrem Anwendungsfall verwendet werden soll.

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