Warum ist ArrayDeque besser als LinkedList?

160

Ich versuche zu verstehen, warum Javas ArrayDeque besser ist als Javas LinkedList, da beide die Deque-Schnittstelle implementieren.

Ich sehe kaum jemanden, der ArrayDeque in seinem Code verwendet. Wenn jemand mehr Licht in die Implementierung von ArrayDeque bringt, wäre dies hilfreich.

Wenn ich es verstehe, werde ich sicherer damit umgehen. Ich konnte die JDK-Implementierung hinsichtlich der Art und Weise, wie Head- und Tail-Referenzen verwaltet werden, nicht klar verstehen.

Grausam
quelle
5
Schauen Sie sich die Antwort in dieser Frage an, die ich vor Tagen gestellt habe: stackoverflow.com/questions/6129805/…
Renato Dinhani
1
In dem Ordner, in dem Sie Ihr JDK installiert haben, befindet sich eine Datei src.zip. Es ist das Archiv mit dem Quellcode der Java-Klassen. Ich empfehle dringend, diese Klassenstruktur und Interna zu studieren, um besser zu verstehen, wie Java-Klassen funktionieren.

Antworten:

155

Verknüpfte Strukturen sind möglicherweise die schlechteste Struktur, die mit einem Cache-Fehler für jedes Element iteriert werden kann. Darüber hinaus verbrauchen sie viel mehr Speicher.

Wenn Sie beide Enden hinzufügen / entfernen müssen, ist ArrayDeque deutlich besser als eine verknüpfte Liste. Der Direktzugriff auf jedes Element ist für eine zyklische Warteschlange ebenfalls O (1).

Die einzig bessere Operation einer verknüpften Liste besteht darin, das aktuelle Element während der Iteration zu entfernen.

bestsss
quelle
56
Ein weiterer zu beachtender Unterschied: LinkedList unterstützt Nullelemente, ArrayDeque jedoch nicht.
Luke Usherwood
14
Ein weiterer kleiner Nachteil (für Echtzeitanwendungen) ist, dass bei einem Push / Add-Vorgang etwas mehr Zeit benötigt wird, wenn das interne Array des ArrayDeque voll ist, da es seine Größe verdoppeln und alle Daten kopieren muss.
Andrei I
4
@AndreiI, das ist nur eine Seite der Geschichte. Selbst wenn Sie die Iterationskosten für Echtzeitanwendungen und die Fähigkeit zur Vorbelegung der erforderlichen Kapazität ausschließen, muss der GC möglicherweise die gesamte LinkedList iterieren. Grundsätzlich verschieben Sie die Kosten (die beim Booten höher sind) in den GC.
Bests
3
@ DavidT. b / c es geht um GC-Kosten des freigegebenen Knotens, das Zuweisen des Kopfes erfordert möglicherweise auch eine Kartenmarkierung (für den GC erneut, wenn sich die LinkedList bereits in der dauerhaften Gen befindet) ... und das zusätzlich zur zusätzlichen Indirektion (Cache-) miss), um das Element zurückzugeben und erneut zu verknüpfen.
Bests
1
LinkedListImplementiert auch, Listwährend ArrayDequedies nicht der Fall ist. Dies bedeutet, LinkedListdass Methoden wie indexOfoder remove(int)während ArrayDequenicht haben. Es kann manchmal wichtig sein.
ZhekaKozlov
60

Ich glaube, dass der größte Leistungsengpass darin LinkedListbesteht, dass die Implementierung hinter den Kulissen immer dann, wenn Sie an ein Ende der Deque drängen, einen neuen Knoten für verknüpfte Listen zuweist, der im Wesentlichen JVM / OS umfasst, und das ist teuer. Wenn Sie von einem beliebigen Ende aus auftauchen, können die internen Knoten von LinkedListfür die Speicherbereinigung verwendet werden, und das ist mehr Arbeit hinter den Kulissen. Da die verknüpften Listenknoten hier und da zugewiesen werden, bietet die Verwendung des CPU-Cache keinen großen Vorteil.

Wenn es von Interesse sein könnte, habe ich einen Beweis dafür, dass das Hinzufügen (Anhängen) eines Elements zu ArrayListoder ArrayDequedie Ausführung in einer amortisierten konstanten Zeit erfolgt; beziehen sich darauf .

coderodde
quelle
26

ArrayDeque ist neu in Java 6, weshalb viele Codes (insbesondere Projekte, die versuchen, mit früheren Java-Versionen kompatibel zu sein) es nicht verwenden.

In einigen Fällen ist dies "besser", da Sie nicht jedem einzufügenden Element einen Knoten zuweisen. Stattdessen werden alle Elemente in einem riesigen Array gespeichert, dessen Größe geändert wird, wenn es voll wird.

Chris Jester-Young
quelle
17

Alle Leute, die a kritisieren LinkedList, denken an jeden anderen Typ, der Listin Java verwendet hat, wahrscheinlich ArrayListund LinkedListmeistens, weil sie vor Java 6 waren und weil dies diejenigen sind, die in den meisten Büchern als Anfang gelehrt werden.

Das heißt aber nicht, dass ich blindlings auf LinkedListder ArrayDequeSeite von oder stehen würde . Wenn Sie es wissen möchten, werfen Sie einen Blick auf den folgenden Benchmark von Brian .

Der Testaufbau berücksichtigt:

  • Jedes Testobjekt besteht aus einer Zeichenfolge mit 500 Zeichen. Jeder String ist ein anderes Objekt im Speicher.
  • Die Größe des Testarrays wird während der Tests variiert.
  • Für jede Kombination aus Arraygröße / Warteschlange und Implementierung werden 100 Tests ausgeführt und die durchschnittliche Zeit pro Test berechnet.
  • Jeder Test besteht darin, jede Warteschlange mit allen Objekten zu füllen und dann alle zu entfernen.
  • Messen Sie die Zeit in Millisekunden.

Testergebnis:

  • Unter 10.000 Elementen wurden sowohl LinkedList- als auch ArrayDeque-Tests auf einer Ebene von unter 1 ms gemittelt.
  • Je größer die Datensätze werden, desto größer werden die Unterschiede zwischen der durchschnittlichen Testzeit von ArrayDeque und LinkedList.
  • Bei der Testgröße von 9.900.000 Elementen dauerte der LinkedList-Ansatz ~ 165% länger als der ArrayDeque-Ansatz.

Graph:

Geben Sie hier die Bildbeschreibung ein

Wegbringen:

  • Wenn in Ihrer Anforderung 100 oder 200 Elemente gespeichert werden, macht die Verwendung einer der Warteschlangen keinen großen Unterschied.
  • Wenn Sie jedoch auf Mobilgeräten entwickeln, möchten Sie möglicherweise eine ArrayListoder ArrayDequemit einer guten Schätzung der maximalen Kapazität verwenden, die die Liste aufgrund strenger Speicherbeschränkungen möglicherweise benötigt.
  • Es gibt eine Menge Code, der mit einem LinkedListso sorgfältigen Profil geschrieben wurde, wenn Sie sich für die Verwendung von a entschieden haben, ArrayDequeinsbesondere, weil es die ListSchnittstelle NICHT implementiert (ich denke, das ist der Grund, der groß genug ist). Es kann sein, dass Ihre Codebasis höchstwahrscheinlich ausführlich mit der List-Oberfläche kommuniziert und Sie sich entscheiden, mit einem zu springen ArrayDeque. Die Verwendung für interne Implementierungen könnte eine gute Idee sein ...
Manish Kumar Sharma
quelle
Wie würde dieser Benchmark die GC-Zeit erfassen, die durch den Müll der verknüpften Liste verursacht wird?
0xbe5077ed
3

ArrayDeque und LinkedList implementieren die Deque- Schnittstelle, die Implementierung ist jedoch unterschiedlich.

Hauptunterschiede:

  1. Die ArrayDeque- Klasse ist die anpassbare Array-Implementierung der Deque- Schnittstelle und die LinkedList- Klasse ist die Listenimplementierung

  2. NULL-Elemente können zu LinkedList hinzugefügt werden, jedoch nicht in ArrayDeque

  3. ArrayDeque ist effizienter als die LinkedList für das Hinzufügen und Entfernen an beiden Enden, und die LinkedList-Implementierung ist effizient zum Entfernen des aktuellen Elements während der Iteration

  4. Die LinkedList- Implementierung belegt mehr Speicher als die ArrayDeque

Wenn Sie also keine NULL-Elemente unterstützen müssen und weniger Speicher und Effizienz beim Hinzufügen / Entfernen von Elementen an beiden Enden suchen, ist ArrayDeque das Beste

Weitere Informationen finden Sie in der Dokumentation .

Ravindra Babu
quelle
13
"In der verknüpften Liste wird O (N) benötigt, um das letzte Element zu finden." ist nicht genau wahr. LinkedList ist als doppelt verknüpfte Liste implementiert, sodass Sie die Liste nicht durchlaufen müssen, um das letzte Element ( header.previous.element) zu erhalten. Die Behauptung "Speichereffizienz" kann auch angefochten werden, da die Größe des Hintergrundarrays immer auf die nächste Potenz von 2
geändert wird
5
"Es wird O (N) brauchen, um das letzte Element zu finden" ist FALSCH. Die verknüpfte Liste enthält einen Verweis auf den letzten Knoten, und LinkedList.descendingIterator () ruft diesen Knoten ab. So erhalten wir O (1) Leistung. Siehe: kaffeeorientiertes Programmieren.wordpress.com/2018/04/23/… (daher Abstimmungen).
Leo Ufimtsev
1
Wenn Sie Iteratormit a auf das letzte Element zugreifen, lautet die Operation für beide Klassen O (N). Wenn Sie die gemeinsame DequeSchnittstelle verwenden, ist der Zugriff auf das letzte Element für beide Klassen O (1). Unabhängig davon, welchen Standpunkt Sie einnehmen, schreiben Sie O (1) zuArrayDequeLinkedList , ist es falsch und O (N) gleichzeitig zuzuweisen.
Holger
Alle Ihre Vorschläge werden aufgenommen und der Inhalt korrigiert
Ravindra babu
1

Obwohl ArrayDeque<E>und LinkedList<E>beide Deque<E>Interface implementiert haben , verwendet ArrayDeque im Grunde genommen das Object-Array, E[]um die Elemente in seinem Objekt zu belassen. Daher verwendet es im Allgemeinen den Index zum Lokalisieren der Head- und Tail-Elemente.

Mit einem Wort, es funktioniert genau wie Deque (mit allen Deque-Methoden), verwendet jedoch die Datenstruktur des Arrays. Welche besser ist, hängt davon ab, wie und wo Sie sie verwenden.

rekinyz
quelle
-1

Das ist nicht immer der Fall.

Zum Beispiel hat im folgenden Fall linkedlisteine bessere Leistung als ArrayDequegemäß Leetcode 103.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> rs=new ArrayList<>();
        if(root==null)
            return rs;
        // 👇 here ,linkedlist works better
        Queue<TreeNode> queue=new LinkedList<>();
        queue.add(root);
        boolean left2right=true;
        while(!queue.isEmpty())
        {
            int size=queue.size();
            LinkedList<Integer> t=new LinkedList<>();
            while(size-->0)
            {
                TreeNode tree=queue.remove();
                if(left2right)  
                    t.add(tree.val);
                else
                    t.addFirst(tree.val);
                if(tree.left!=null)
                {
                    queue.add(tree.left);
                }
                if(tree.right!=null)
                {
                    queue.add(tree.right);
                }
            }
            rs.add(t);
            left2right=!left2right;
        }
        return rs;
    }
}
rot
quelle
-5

Die zeitliche Komplexität für ArrayDeque für den Zugriff auf ein Element beträgt O (1) und für LinkList O (N) für den Zugriff auf das letzte Element. ArrayDeque ist nicht threadsicher, daher ist eine manuelle Synchronisierung erforderlich, damit Sie über mehrere Threads darauf zugreifen können und diese schneller sind.

Piyush Yawalkar
quelle
3
Wenn Sie sich auf LinkedListJava beziehen Collection, ist es doppelt verknüpft und hat schnellen Zugriff auf Kopf und Schwanz, sodass der Zugriff auf das letzte Element auch O (1) erfordert.
Maurice
Der Zugriff auf das letzte Element in LinkedList ist nicht O (N). Wenn Sie descendingIterator () verwenden, wird dies in O (1) ausgeführt. Siehe kaffeeorientierte Programmierung.wordpress.com/2018/04/23/… (daher nach unten stimmen).
Leo Ufimtsev
1
Beide Klassen sind nicht threadsicher. Und es gibt keinen Zusammenhang zwischen dem Anfang des Satzes "Manuelle Synchronisation ist notwendig" und seinem Ende "Sie sind schneller".
Holger