3598 Stimmen

Wann sollte man LinkedList statt ArrayList in Java verwenden?

Ich war schon immer jemand, der einfach nur benutzt:

List<String> names = new ArrayList<>();

Ich verwende die Schnittstelle als Typname für Tragbarkeit damit ich, wenn ich Fragen wie diese stelle, meinen Code überarbeiten kann.

Wann sollte LinkedList verwendet werden über ArrayList und andersherum?

10 Stimmen

12 Stimmen

Siehe dazu das Zitat des Autors von LinkedList stackoverflow.com/a/42529652/2032701 und Sie bekommen ein praktisches Gefühl für das Thema.

2 Stimmen

Niemals. Ich habe es einmal in meinen 25 Jahren Java-Programmierung getan und es im Nachhinein bereut.

122voto

Daniel Martin Punkte 22383

Ja, ich weiß, das ist eine uralte Frage, aber ich werde meinen Senf dazugeben:

LinkedList ist fast immer die falsche Wahl, was die Leistung angeht. Es gibt einige sehr spezifische Algorithmen, bei denen eine LinkedList erforderlich ist, aber diese sind sehr, sehr selten und der Algorithmus wird in der Regel speziell von der Fähigkeit der LinkedList abhängen, Elemente in der Mitte der Liste relativ schnell einzufügen und zu löschen, sobald man mit einem ListIterator dorthin navigiert hat.

Es gibt einen häufigen Anwendungsfall, in dem LinkedList besser abschneidet als ArrayList: der einer Warteschlange. Wenn Ihr Ziel jedoch die Leistung ist, sollten Sie anstelle von LinkedList auch eine ArrayBlockingQueue in Betracht ziehen (wenn Sie im Voraus eine Obergrenze für die Größe der Warteschlange festlegen können und es sich leisten können, den gesamten Speicher im Voraus zuzuweisen), oder dies Implementierung von CircularArrayList . (Ja, es ist von 2001, also müssen Sie es generieren, aber ich habe vergleichbare Leistungskennzahlen wie die im Artikel genannten in einer aktuellen JVM erhalten)

41 Stimmen

1 Stimmen

ArrayDeque ist langsamer als LinkedList es sei denn, alle Vorgänge befinden sich am selben Ende. Es ist OK, wenn es als Stapel verwendet wird, aber es macht keine gute Warteschlange.

4 Stimmen

Falsch - zumindest für Oracles Implementierung in jdk1.7.0_60 und im folgenden Test. Ich habe einen Test erstellt, bei dem ich 10 Millionen Mal eine Schleife durchführe und eine Deque mit 10 Millionen zufälligen Ganzzahlen habe. Innerhalb der Schleife frage ich ein Element ab und biete ein konstantes Element an. Auf meinem Computer ist LinkedList über 10 mal langsamer als ArrayDeque und verbraucht weniger Speicher). Der Grund dafür ist, dass ArrayDeque im Gegensatz zu ArrayList einen Zeiger auf den Kopf des Arrays behält, so dass es nicht alle Elemente verschieben muss, wenn der Kopf entfernt wird.

87voto

dgtized Punkte 2844

Es ist eine Frage der Effizienz. LinkedList ist schnell beim Hinzufügen und Löschen von Elementen, aber langsam beim Zugriff auf ein bestimmtes Element. ArrayList ist schnell, wenn es um den Zugriff auf ein bestimmtes Element geht, kann aber langsam sein, wenn es um das Hinzufügen an einem der Enden geht, und besonders langsam beim Löschen in der Mitte.

Array vs. ArrayList vs. LinkedList vs. Vektor geht mehr in die Tiefe, ebenso wie die Verknüpfte Liste .

10 Stimmen

Erwähnenswert ist hier, dass LinkedList schnell ist, um nur an der ersten und letzten Position etwas hinzuzufügen/zu entfernen - dann ist die Komplexität O(1), aber das Hinzufügen in der Mitte ist immer noch O(n), weil wir durch etwa n/2 Elemente von LinkedList .

1 Stimmen

Aber ist es das? Warum ist eine ArrayList immer schneller als eine LinkedList festgestellt, dass das Hinzufügen von 10M Elementen zu einer ArrayList schneller war als das Hinzufügen von 10M Elementen zu einer LinkedList. (d.h. ArrayList ist schneller, wenn das Hinzufügen am Ende, amortisiert über mit manchmal neu zuordnen).

61voto

Ash Punkte 2041

Richtig oder falsch: Bitte führen Sie den Test lokal durch und entscheiden Sie selbst!

Bearbeiten/Entfernen ist schneller in LinkedList als ArrayList .

ArrayList unterstützt durch Array die doppelt so groß sein muss, ist bei großvolumigen Anwendungen schlechter.

Nachfolgend finden Sie die Ergebnisse der Einheitstests für jeden Vorgang, wobei die Zeitangaben in Nanosekunden erfolgen.


Operation                       ArrayList                      LinkedList  

AddAll   (Insert)               101,16719                      2623,29291 

Add      (Insert-Sequentially)  152,46840                      966,62216

Add      (insert-randomly)      36527                          29193

remove   (Delete)               20,56,9095                     20,45,4904

contains (Search)               186,15,704                     189,64,981

Hier ist der Code:

import org.junit.Assert;
import org.junit.Test;

import java.util.*;

public class ArrayListVsLinkedList {
    private static final int MAX = 500000;
    String[] strings = maxArray();

    ////////////// ADD ALL ////////////////////////////////////////
    @Test
    public void arrayListAddAll() {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX);

        watch.start();
        arrayList.addAll(stringList);
        watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
    }

    @Test
    public void linkedListAddAll() throws Exception {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);

        watch.start();
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(stringList);
        watch.totalTime("Linked List addAll() = ");  //2623,29291 Nanoseconds
    }

    //Note: ArrayList is 26 time faster here than LinkedList for addAll()

    ///////////////// INSERT /////////////////////////////////////////////
    @Test
    public void arrayListAdd() {
        Watch watch = new Watch();
        List<String> arrayList = new ArrayList<String>(MAX);

        watch.start();
        for (String string : strings)
            arrayList.add(string);
        watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
    }

    @Test
    public void linkedListAdd() {
        Watch watch = new Watch();

        List<String> linkedList = new LinkedList<String>();
        watch.start();
        for (String string : strings)
            linkedList.add(string);
        watch.totalTime("Linked List add() = ");  //966,62216 Nanoseconds
    }

    //Note: ArrayList is 9 times faster than LinkedList for add sequentially

    /////////////////// INSERT IN BETWEEN ///////////////////////////////////////

    @Test
    public void arrayListInsertOne() {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
        arrayList.addAll(stringList);

        String insertString0 = getString(true, MAX / 2 + 10);
        String insertString1 = getString(true, MAX / 2 + 20);
        String insertString2 = getString(true, MAX / 2 + 30);
        String insertString3 = getString(true, MAX / 2 + 40);

        watch.start();

        arrayList.add(insertString0);
        arrayList.add(insertString1);
        arrayList.add(insertString2);
        arrayList.add(insertString3);

        watch.totalTime("Array List add() = ");//36527
    }

    @Test
    public void linkedListInsertOne() {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(stringList);

        String insertString0 = getString(true, MAX / 2 + 10);
        String insertString1 = getString(true, MAX / 2 + 20);
        String insertString2 = getString(true, MAX / 2 + 30);
        String insertString3 = getString(true, MAX / 2 + 40);

        watch.start();

        linkedList.add(insertString0);
        linkedList.add(insertString1);
        linkedList.add(insertString2);
        linkedList.add(insertString3);

        watch.totalTime("Linked List add = ");//29193
    }

    //Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.

    ////////////////// DELETE //////////////////////////////////////////////////////
    @Test
    public void arrayListRemove() throws Exception {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX);

        arrayList.addAll(stringList);
        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        arrayList.remove(searchString0);
        arrayList.remove(searchString1);
        watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
    }

    @Test
    public void linkedListRemove() throws Exception {
        Watch watch = new Watch();
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(Arrays.asList(strings));

        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        linkedList.remove(searchString0);
        linkedList.remove(searchString1);
        watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
    }

    //Note: LinkedList is 10 millisecond faster than ArrayList while removing item.

    ///////////////////// SEARCH ///////////////////////////////////////////
    @Test
    public void arrayListSearch() throws Exception {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX);

        arrayList.addAll(stringList);
        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        arrayList.contains(searchString0);
        arrayList.contains(searchString1);
        watch.totalTime("Array List addAll() time = ");//186,15,704
    }

    @Test
    public void linkedListSearch() throws Exception {
        Watch watch = new Watch();
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(Arrays.asList(strings));

        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        linkedList.contains(searchString0);
        linkedList.contains(searchString1);
        watch.totalTime("Linked List addAll() time = ");//189,64,981
    }

    //Note: Linked List is 500 Milliseconds faster than ArrayList

    class Watch {
        private long startTime;
        private long endTime;

        public void start() {
            startTime = System.nanoTime();
        }

        private void stop() {
            endTime = System.nanoTime();
        }

        public void totalTime(String s) {
            stop();
            System.out.println(s + (endTime - startTime));
        }
    }

    private String[] maxArray() {
        String[] strings = new String[MAX];
        Boolean result = Boolean.TRUE;
        for (int i = 0; i < MAX; i++) {
            strings[i] = getString(result, i);
            result = !result;
        }
        return strings;
    }

    private String getString(Boolean result, int i) {
        return String.valueOf(result) + i + String.valueOf(!result);
    }
}

1 Stimmen

ArrayList muss nicht verdoppelt werden, um genau zu sein. Bitte prüfen Sie zuerst die Quellen.

0 Stimmen

Es sei darauf hingewiesen, dass Ihr Beispiel fehlerhaft ist... Sie entfernen aus der Zeichenkette zwischen: 18 + [2, 12] Bytes ("true0false", "true500000false"), im Durchschnitt 25 Bytes, die die Größe der Elemente in der Mitte sind. Es ist bekannt, dass eine verknüpfte Liste mit zunehmender Elementgröße besser abschneidet, während ein zusammenhängendes Array (Liste) mit zunehmender Listengröße besser abschneidet. Am wichtigsten ist, dass Sie .equals() auf Strings anwenden, was keine billige Operation ist. Wenn Sie stattdessen Ganzzahlen verwenden würden, wäre das sicher ein Unterschied.

0 Stimmen

- und das ist wahrscheinlich auch der Grund, warum es bei remove/contains so wenig Unterschiede gibt.

57voto

Ryan Punkte 2747

ArrayList ist im Wesentlichen ein Array. LinkedList ist als doppelt verkettete Liste implementiert.

El get ist ziemlich klar. O(1) für ArrayList denn ArrayList einen zufälligen Zugriff über einen Index ermöglichen. O(n) für LinkedList weil es zuerst den Index finden muss. Hinweis: Es gibt verschiedene Versionen von add y remove .

LinkedList ist schneller bei add und remove, aber langsamer bei get. Kurz und gut, LinkedList sollte bevorzugt werden, wenn:

  1. es gibt keine große Anzahl von Zufallszugriffen auf Elemente
  2. es gibt eine große Anzahl von Hinzufügen/Entfernen-Vorgängen

\=== ArrayList \===

  • add(E e)
    • am Ende von ArrayList hinzufügen
    • Kosten für die Änderung der Speichergröße erfordern.
    • O(n) schlechteste, O(1) amortisiert
  • add(int index, E element)
    • zu einer bestimmten Indexposition hinzufügen
    • Kosten für die Verschiebung und mögliche Größenänderung des Speichers erfordern
    • O(n)
  • remove(int index)
    • ein bestimmtes Element entfernen
    • Kosten für die Verschiebung und mögliche Größenänderung des Speichers erfordern
    • O(n)
  • entfernen(Objekt o)
    • das erste Vorkommen des angegebenen Elements aus dieser Liste entfernen
    • Zuerst muss das Element gesucht werden, dann werden die Kosten für die Verschiebung und die eventuelle Änderung der Speichergröße berechnet.
    • O(n)

\=== LinkedList \===

  • add(E e)

    • an das Ende der Liste anfügen
    • O(1)
  • add(int index, E element)

    • an der angegebenen Position einfügen
    • Sie müssen zuerst die Position finden
    • O(n)
  • entfernen()
    • erstes Element der Liste entfernen
    • O(1)
  • remove(int index)
    • Element mit angegebenem Index entfernen
    • Sie müssen zuerst das Element finden
    • O(n)
  • entfernen(Objekt o)
    • das erste Vorkommen des angegebenen Elements entfernen
    • Sie müssen zuerst das Element finden
    • O(n)

Hier ist eine Zahl aus programcreek.de ( add y remove sind vom ersten Typ, d. h. sie fügen ein Element am Ende der Liste hinzu und entfernen das Element an der angegebenen Position in der Liste):

enter image description here

4 Stimmen

"LinkedList ist schneller als add/remove". Falsch, siehe die Antwort oben stackoverflow.com/a/7507740/638670

54voto

Lior Bar-On Punkte 9586

TL;DR aufgrund der modernen Computerarchitektur, ArrayList für nahezu jeden möglichen Anwendungsfall deutlich effizienter sein - und daher LinkedList sollte außer in einigen sehr speziellen und extremen Fällen vermieden werden.


Theoretisch hat LinkedList eine O(1) für die add(E element)

Auch das Hinzufügen eines Elements in der Mitte einer Liste sollte sehr effizient sein.

Die Praxis ist ganz anders, denn LinkedList ist eine Cache Feind Datenstruktur. Aus Sicht der Leistung gibt es nur sehr wenige Fälle, in denen LinkedList könnte leistungsfähiger sein als die Cache-freundlich ArrayList .

Hier sind die Ergebnisse eines Benchmark-Tests zum Einfügen von Elementen an zufälligen Stellen. Wie Sie sehen können, ist die Array-Liste viel effizienter, obwohl theoretisch jedes Einfügen in der Mitte der Liste ein "Verschieben" der n spätere Elemente des Arrays (kleinere Werte sind besser):

enter image description here

Bei der Arbeit mit einer neueren Hardware-Generation (größere, effizientere Caches) sind die Ergebnisse noch aussagekräftiger:

enter image description here

LinkedList braucht viel mehr Zeit, um die gleiche Aufgabe zu erfüllen. Quelle Quellcode

Hierfür gibt es zwei Hauptgründe:

  1. Hauptsächlich - dass die Knotenpunkte des LinkedList nach dem Zufallsprinzip über den Speicher verstreut sind. RAM ("Random Access Memory") ist nicht wirklich zufällig und Speicherblöcke müssen in den Zwischenspeicher geholt werden. Dieser Vorgang nimmt Zeit in Anspruch, und wenn solche Abrufe häufig erfolgen, müssen die Speicherseiten im Cache ständig ersetzt werden -> Cache-Misses -> Cache ist nicht effizient. ArrayList Elemente werden in einem kontinuierlichen Speicher gespeichert - und das ist genau das, wofür die moderne CPU-Architektur optimiert ist.

  2. Sekundäres LinkedList erforderlich, um Rückwärts-/Vorwärtszeiger zu halten, was den dreifachen Speicherverbrauch pro gespeichertem Wert bedeutet im Vergleich zu ArrayList .

DynamicIntArray ist übrigens eine eigene ArrayList-Implementierung, die Int (primitiver Typ) und nicht Objekte - daher werden alle Daten wirklich nebeneinander gespeichert, was noch effizienter ist.

Ein Schlüsselelement, das man sich merken sollte, ist, dass die Kosten für das Abrufen eines Speicherblocks bedeutender sind als die Kosten für den Zugriff auf eine einzelne Speicherzelle. Aus diesem Grund ist das Lesen von 1 MB sequentiellem Speicher bis zu 400 Mal schneller als das Lesen dieser Datenmenge aus verschiedenen Speicherblöcken:

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

Quelle: Latenzzahlen, die jeder Programmierer kennen sollte

Um die Sache noch deutlicher zu machen, prüfen Sie bitte den Benchmark für das Hinzufügen von Elementen am Anfang der Liste. Dies ist ein Anwendungsfall, bei dem theoretisch die LinkedList sollte wirklich glänzen, und ArrayList sollten schlechte oder sogar schlechtere Ergebnisse aufweisen:

enter image description here

Hinweis: Dies ist ein Benchmark der C++ Std lib, aber meine bisherigen Erfahrungen zeigen, dass die Ergebnisse in C++ und Java sehr ähnlich sind. Quellcode

Das Kopieren einer sequentiellen Speichermenge ist ein Vorgang, der von den modernen CPUs optimiert wurde - was wiederum die Theorie und die Praxis verändert, ArrayList / Vector wesentlich effizienter


Credits: Alle hier veröffentlichten Benchmarks wurden erstellt von Kjell Hedström . Noch mehr Daten finden Sie auf sein Blog

0 Stimmen

Ich würde eine Warteschlange nicht als einzigartig oder extrem bezeichnen! Eine Fifo-Warteschlange ist viel einfacher auf einer LinkedList statt einer ArrayList implementiert. Es ist eigentlich ein Alptraum auf einem ArrayList, wie Sie Ihre eigenen Start, Stopp und tun Ihre eigenen reallocating verfolgen müssen, könnten Sie genauso gut ein Array verwenden, aber eine LinkedList IST ein Fifo. Ich bin mir nicht sicher über die Java-Implementierung, aber eine LinkedList kann O(1) für beide Warteschlangen- und Dequeue-Operationen tun (Erfordert einen speziellen Zeiger auf das Schwanz-Element für das Entfernen, die ich vermute, Java hat, aber ich habe nicht doppelt überprüft).

2 Stimmen

Einfügen in die Mitte eines ArrayList Array verwendet die native Methode java.lang.System.arraycopy() die im OpenJDK in C++ geschrieben ist. Während also theoretisch eine LinkedList hat in der Praxis weniger Arbeit zu tun, da es so viele außersprachliche Mechanismen gibt, die das "Big O" weitgehend irrelevant machen. Vor allem, wie cache-freundlich die Dinge sind, wie diese ausgezeichnete Antwort zeigt.

0 Stimmen

Danke, aber mit dem letzten Benchmark stimmt etwas nicht. 1) Warum wächst die Dauer der "Liste" überhaupt? Wenn die Elemente immer am Anfang (Index 0) eingefügt werden, ist das nicht von der Größe abhängig. Und wenn Sie das Einfügen um den Anfang herum meinten, dann spielt es eine große Rolle, wie nahe dieses "herum" ist - in Java ist das Einfügen des 1000sten Elements in das vorgefertigte 100_000 Array (mehrmals) immer noch schneller für LinkedList und wird nur langsamer, wenn man sich dem Ende nähert. 2) Also im Moment um-Start-Einfügen in Java ist immer noch schneller für LinkedList. Allerdings würde ich hier zu einem Trick raten - kehren Sie die Liste einfach um, bevor Sie mit ihr arbeiten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X