Quando utilizzare LinkedList su ArrayList in Java?


3123

Sono sempre stato uno da usare semplicemente:

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

Uso l'interfaccia come nome del tipo per la portabilità , in modo che quando faccio domande come queste posso rielaborare il mio codice.

Quando dovrebbe LinkedListessere usato sopra ArrayListe viceversa?



1
Basta vedere la citazione dell'autore di LinkedList stackoverflow.com/a/42529652/2032701 e avrai un'idea pratica del problema.
Ruslan,

Vedere il post Java LinkedList vs ArrayList , che descrive le differenze e include alcuni test delle prestazioni.
alonana,

Risposte:


3371

Il riepilogo ArrayList con ArrayDequeè preferibile in molti più casi d'uso rispetto a LinkedList. Se non sei sicuro, inizia con ArrayList.


LinkedListe ArrayListsono due diverse implementazioni dell'interfaccia Elenco. LinkedListlo implementa con un elenco doppiamente collegato.ArrayListlo implementa con una matrice di ridimensionamento dinamico.

Come per le operazioni standard di elenco e array collegati, i vari metodi avranno runtime algoritmici diversi.

Per LinkedList<E>

  • get(int index)è O (n) (con n / 4 passi in media), ma O (1) quando index = 0o index = list.size() - 1(in questo caso, puoi anche usare getFirst()e getLast()). Uno dei principali vantaggi di LinkedList<E>
  • add(int index, E element)è O (n) (con n / 4 passi in media), ma O (1) quando index = 0o index = list.size() - 1(in questo caso, puoi anche usare addFirst()e addLast()/ add()). Uno dei principali vantaggi di LinkedList<E>
  • remove(int index)è O (n) (con n / 4 passi in media), ma O (1) quando index = 0o index = list.size() - 1(in questo caso, puoi anche usare removeFirst()e removeLast()). Uno dei principali vantaggi di LinkedList<E>
  • Iterator.remove()è O (1) . Uno dei principali vantaggi di LinkedList<E>
  • ListIterator.add(E element)è O (1) . Uno dei principali vantaggi di LinkedList<E>

Nota: molte operazioni richiedono n / 4 passaggi in media, numero costante di passaggi nel migliore dei casi (ad esempio indice = 0) e n / 2 passaggi nel caso peggiore (in mezzo all'elenco)

Per ArrayList<E>

  • get(int index)è O (1) . Vantaggio principale di ArrayList<E>
  • add(E element)è O (1) ammortizzato, ma O (n) nel caso peggiore poiché l'array deve essere ridimensionato e copiato
  • add(int index, E element)è O (n) (con n / 2 passi in media)
  • remove(int index)è O (n) (con n / 2 passi in media)
  • Iterator.remove()è O (n) (con n / 2 passi in media)
  • ListIterator.add(E element)è O (n) (con n / 2 passi in media)

Nota: molte operazioni richiedono n / 2 passaggi in media, numero costante di passaggi nel migliore dei casi (fine dell'elenco), n passaggi nel caso peggiore (inizio dell'elenco)

LinkedList<E>consente inserimenti o rimozioni a tempo costante mediante iteratori , ma solo l'accesso sequenziale agli elementi. In altre parole, puoi spostare l'elenco in avanti o indietro, ma trovare una posizione nell'elenco richiede tempo proporzionale alla dimensione dell'elenco. Javadoc afferma che "le operazioni che indicizzano nella lista attraverseranno la lista dall'inizio o dalla fine, a seconda di quale sia più vicina" , quindi quei metodi sono O (n) ( n / 4 passi) in media, sebbene O (1) per index = 0.

ArrayList<E>d'altra parte, consente un rapido accesso casuale in lettura, in modo da poter afferrare qualsiasi elemento in tempo costante. Ma aggiungere o rimuovere da qualsiasi luogo tranne la fine richiede di spostare tutti questi ultimi elementi, sia per fare un'apertura che per colmare il divario. Inoltre, se aggiungi più elementi della capacità dell'array sottostante, viene allocato un nuovo array (1,5 volte la dimensione) e il vecchio array viene copiato in quello nuovo, quindi l'aggiunta a un ArrayListè O (n) nel peggiore caso ma costante in media.

Quindi, a seconda delle operazioni che intendi fare, dovresti scegliere le implementazioni di conseguenza. Scorrere su entrambi i tipi di Elenco è praticamente ugualmente economico. (Iterazione su unArrayList è tecnicamente più veloce, ma a meno che tu non stia facendo qualcosa di veramente sensibile alle prestazioni, non dovresti preoccuparti di questo - sono entrambe costanti.)

I principali vantaggi dell'utilizzo di a LinkedListsorgono quando si riutilizzano gli iteratori esistenti per inserire e rimuovere elementi. Queste operazioni possono quindi essere eseguite in O (1) modificando l'elenco solo localmente. In un elenco di array, il resto dell'array deve essere spostato (ovvero copiato). Dall'altro lato, cercare in un LinkedListmodo seguendo i collegamenti in O (n) ( n / 2 passi) per il caso peggiore, mentre in una ArrayListposizione desiderata può essere calcolato matematicamente e accessibile in O (1) .

Un altro vantaggio dell'utilizzo di a LinkedListsorge quando si aggiunge o si rimuove dalla testa dell'elenco, poiché tali operazioni sono O (1) , mentre sono O (n) per ArrayList. Nota che ArrayDequepuò essere una buona alternativa LinkedListper l'aggiunta e la rimozione dalla testa, ma non lo è List.

Inoltre, se si dispone di elenchi di grandi dimensioni, tenere presente che anche l'utilizzo della memoria è diverso. Ogni elemento di a LinkedListha un overhead maggiore poiché vengono memorizzati anche i puntatori agli elementi successivo e precedente. ArrayListsnon avere questo sovraccarico. Tuttavia, ArrayListsoccupa tutta la memoria allocata per la capacità, indipendentemente dal fatto che siano stati effettivamente aggiunti elementi.

La capacità iniziale predefinita di un ArrayListè piuttosto piccola (10 da Java 1.4 - 1.8). Ma poiché l'implementazione sottostante è un array, l'array deve essere ridimensionato se si aggiungono molti elementi. Per evitare il costo elevato del ridimensionamento quando sai che stai per aggiungere molti elementi, costruisci ArrayListcon una capacità iniziale più elevata.


183
Hai dimenticato di menzionare i costi di inserimento. In una LinkedList, una volta ottenuta la posizione corretta, l'inserimento costa O (1), mentre in una ArrayList sale a O (n) - tutti gli elementi oltre il punto di inserimento devono essere spostati.
David Rodríguez - dribeas,

26
Per quanto riguarda l'uso di Vector: non è necessario ricorrere a Vector. Il modo per farlo è con l'implementazione dell'elenco preferito e una chiamata a synchronizedList per dargli un wrapper sincronizzato. Vedi: java.sun.com/docs/books/tutorial/collections/implementations/…
Ryan Cox,

69
No, per una LinkedList, get è ancora O (n) anche se si conosce la posizione, perché per arrivare a quella posizione, l'implementazione sottostante deve percorrere i puntatori "successivi" dell'elenco collegato per arrivare al valore di quella posizione. Non esiste un accesso casuale. Per la posizione 2, camminare con i puntatori potrebbe essere economico, ma per la posizione 1 milione, non così economico. Il punto è che è proporzionale alla posizione, il che significa che è O (n).
Jonathan Tran,

53
@Kevin Potrebbe importare che la memoria sia "ravvicinata". L'hardware memorizzerà nella cache i blocchi contigui di memoria (RAM dinamica) in una RAM statica più veloce nella cache L1 o L2. In teoria e la maggior parte delle volte praticamente, la memoria può essere trattata come un accesso casuale. Ma in realtà, leggere la memoria in sequenza sarà leggermente più veloce che in ordine casuale. Per un ciclo critico per le prestazioni, questo potrebbe importare. Lo chiamano "località spaziale" o località di riferimento .
Jonathan Tran,

92
Non esiste una cosa come O(n/2)o O(n/4). La grande notazione O ti dice come un'operazione si ridimensiona con n maggiore . e un'operazione che richiede n/2passaggi si ridimensiona esattamente come un'operazione che richiede npassaggi, motivo per cui vengono rimossi sommi o fattori costanti. O(n/2)e O(n/4)sono entrambi giusti O(n). LinkedListe ArrayListhanno comunque diversi fattori costanti, quindi non avrebbe senso confrontare uno O(n/2)di uno con uno O(n/4)dell'altro, entrambi denotano semplicemente operazioni di ridimensionamento lineare.
Holger,

630

Finora, nessuno sembra aver affrontato l'impronta di memoria di ciascuna di queste liste oltre al consenso generale sul fatto che a LinkedListsia "molto di più" di un ArrayListcosì ho fatto un po 'di scricchiolio per dimostrare esattamente quanto entrambe le liste occupano per N riferimenti null.

Dato che i riferimenti sono a 32 o 64 bit (anche quando nulli) sui rispettivi sistemi, ho incluso 4 set di dati per 32 e 64 bit LinkedListse ArrayLists.

Nota: le dimensioni mostrate per le ArrayListlinee sono per gli elenchi tagliati : in pratica, la capacità dell'array di supporto in un ArrayListè generalmente maggiore del conteggio degli elementi corrente.

Nota 2: (grazie a BeeOnRope) Dato che CompressedOops è predefinito da metà JDK6 in poi, i valori sottostanti per le macchine a 64 bit corrisponderanno sostanzialmente alle loro controparti a 32 bit, a meno che ovviamente non lo si spenga in modo specifico.


Grafico di LinkedList e ArrayList No. di elementi x byte


Il risultato mostra chiaramente che LinkedListè molto più che ArrayList, soprattutto con un conteggio di elementi molto elevato. Se la memoria è un fattore, stai alla larga LinkedLists.

Le formule che ho usato seguono, fammi sapere se ho fatto qualcosa di sbagliato e lo risolverò. 'b' è 4 o 8 per i sistemi a 32 o 64 bit e 'n' è il numero di elementi. Nota che il motivo delle mod è perché tutti gli oggetti in Java occuperanno uno spazio multiplo di 8 byte indipendentemente dal fatto che sia tutto usato o meno.

Lista di array:

ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 8) == 8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8) + MOD(8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8), 8)

Lista collegata:

LinkedList object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 8) == 8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n + MOD(8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n, 8)


2
Abbastanza interessante vedere che LinkedList richiede tutta la memoria di ArrayList per la memorizzazione di un singolo elemento. Che non intuitivo! Cosa succede se esegui il tuo esempio con -XX: + UseCompressedOops?
jontejj,

215
Il problema con la tua matematica è che il tuo grafico esagera notevolmente l'impatto. Stai modellando oggetti che contengono ciascuno solo un int, quindi 4 o 8 byte di dati. Nell'elenco collegato, ci sono essenzialmente 4 "parole" di sovraccarico. Il tuo grafico dà quindi l'impressione che gli elenchi collegati utilizzino "cinque volte" la memorizzazione degli elenchi di array. Questo è sbagliato. Il sovraccarico è di 16 o 32 byte per oggetto, come regolazione aggiuntiva, non come fattore di ridimensionamento.
Heath Hunnicutt,

6
Nessuno degli oggetti ArrayList / LinkedList / Node contiene solo un int, quindi non capisco quello che stai dicendo lì. Ho chiarito 'overhead dell'oggetto' in 'intestazione oggetto' per chiarire - esiste un'intestazione di 8 byte per ogni oggetto indipendentemente dal sistema, e sì che include tutti gli oggetti Node in LinkedList, che sono tutti conteggiati correttamente per quanto posso raccontare. Per inciso, nel guardarlo di nuovo, ho trovato un paio di altri problemi con la mia matematica in LinkedList che in realtà peggiora la divisione e ArrayList . Sono felice di continuare ad aggiornarlo, quindi non esitare a chiarire ed elaborare ulteriormente.
Numerone,

6
Va notato che CompressedOopsora è predefinito in tutti i JDK recenti (7, 8 e aggiornamenti di 6 per alcuni anni), quindi 64 bit non farà differenza ArrayListo LinkedListdimensioni, a meno che tu non abbia esplicitamente disattivato oops compresso per qualche ragione.
BeeOnRope,

1
@jontejj l'aumento di capacità predefinito è del 50%, quindi quando si popola un ArrayListsenza specificare una capacità iniziale, utilizzerà comunque una quantità di memoria significativamente inferiore rispetto a un LinkedList.
Holger,

244

ArrayListè quello che vuoi. LinkedListè quasi sempre un bug (prestazionale).

Perché fa LinkedListschifo:

  • Utilizza molti piccoli oggetti di memoria e pertanto influisce sulle prestazioni in tutto il processo.
  • Molti piccoli oggetti sono dannosi per la localizzazione della cache.
  • Qualsiasi operazione indicizzata richiede un attraversamento, ovvero ha prestazioni O (n). Questo non è ovvio nel codice sorgente, portando ad algoritmi O (n) più lenti di quelli ArrayListusati.
  • Ottenere buone prestazioni è difficile.
  • Anche quando le prestazioni della Big-O sono le stesse ArrayList, probabilmente sarà significativamente più lenta.
  • È stonante vedere LinkedListalla fonte perché è probabilmente la scelta sbagliata.

237
Scusate. ti ha segnato. LinkedList non fa schifo. Ci sono situazioni in cui LinkedList è la classe corretta da usare. Sono d'accordo che non ci sono molte situazioni in cui è meglio di un arraylist, ma esistono. Educare le persone che fanno cose stupide!
David Turner,

40
Mi dispiace vedere che hai ottenuto molti voti negativi per questo. Ci sono davvero pochissime ragioni per usare il LinkedList di Java. Oltre alle cattive prestazioni, utilizza anche molta più memoria rispetto alle altre classi List concrete (ogni nodo ha due puntatori aggiuntivi e ogni nodo è un oggetto wrapper separato con i byte di overhead che ne derivano).
Kevin Brock,

42
Questa è una delle risposte più utili qui. È un peccato che molti programmatori non riescano a capire (a) la differenza tra tipi di dati astratti e implementazioni concrete, e (b) l'importanza del mondo reale di fattori costanti e sovraccarico di memoria nel determinare le prestazioni.
Porculus,

50
-1: questa è una vista piuttosto ammiccata. Sì, è vero che ArrayList è uno strumento molto versatile. Tuttavia, ha i suoi limiti. Ci sono casi in cui ciò può causare problemi e dovrai usare LinkedList. Certo, è una soluzione molto specializzata e, come qualsiasi strumento specializzato, nella maggior parte dei casi è sovraperformata da una versatile. Ma ciò non significa che "fa schifo" o qualcosa del genere, devi solo sapere quando usarlo.
Malcolm,

27
@DavidTurner: Esistono, ma penso che il punto di Tom fosse che, se devi chiedere, probabilmente vorrai ArrayList.
user541686,

139

Come qualcuno che si occupa di ingegneria delle prestazioni operative su servizi web SOA su larga scala da circa un decennio, preferirei il comportamento di LinkedList rispetto a ArrayList. Mentre il throughput allo stato stazionario di LinkedList è peggiore e quindi potrebbe portare all'acquisto di più hardware - il comportamento di ArrayList sotto pressione potrebbe portare ad app in un cluster che espandono i loro array in quasi sincronicità e per grandi dimensioni di array potrebbe portare a una mancanza di reattività nell'app e un'interruzione, mentre sotto pressione, che è un comportamento catastrofico.

Allo stesso modo, puoi ottenere un throughput migliore in un'app dal Garbage Collector con throughput predefinito, ma una volta ottenute app java con heap da 10 GB puoi concludere il blocco dell'app per 25 secondi durante un GC completo che provoca timeout e guasti nelle app SOA e fa esplodere i tuoi SLA se si verifica troppo spesso. Anche se il collector CMS richiede più risorse e non raggiunge lo stesso throughput non elaborato, è una scelta molto migliore perché ha una latenza più prevedibile e più piccola.

ArrayList è solo una scelta migliore per le prestazioni se tutto ciò che intendi per prestazioni è la velocità effettiva e puoi ignorare la latenza. Nella mia esperienza nel mio lavoro non posso ignorare la latenza nel caso peggiore.


8
Un'altra soluzione non sarebbe gestire la dimensione dell'elenco a livello di codice utilizzando il metodo sureCapacity () di ArrayList? La mia domanda è: perché molte cose vengono archiviate in un mucchio di fragili strutture dati quando potrebbero essere meglio archiviate in un meccanismo di cache o db? L'altro giorno ho avuto un'intervista in cui hanno imprecato su e giù sui mali di ArrayList, ma vengo qui e trovo che l'analisi della complessità sia del tutto migliore! OTTIMO PUNTO DI DISCUSSIONE, PENSIERO. GRAZIE!
ingyhere il

22
una volta ottenute le app java con heap da 10 GB, puoi finire per bloccare l'app per 25 secondi durante un GC completo che causa timeout In realtà con LinkedList uccidi il garbage collector durante GC completi, deve iterare il LinkedList troppo grande con cache miss on ogni nodo.
bestsss

5
Questa è ... una soluzione orribile. fondamentalmente fai affidamento sul GC che ti ripulisce, il che è incredibilmente costoso, quando puoi semplicemente chiamare sureCapacity () su un arraylist invece ...
Philip Devine,

5
@Andreas: A alloca LinkedList sempre cinque volte la memoria rispetto a un semplice array di riferimenti, quindi un tempo che ArrayListrichiede 2,5 volte consuma ancora molta meno memoria, anche se la memoria non viene recuperata. Poiché l'allocazione di array di grandi dimensioni ignora lo spazio Eden, non hanno alcun impatto sul comportamento GC, a meno che non ci sia davvero abbastanza memoria, nel qual caso, l' LinkedListesplosione è avvenuta molto prima ...
Holger,

5
@Andreas L'altro problema è come viene allocata la memoria. LinkedListè necessario solo un piccolo pezzo di memoria libera da allocare per l'elemento successivo. ArrayListsarà necessario un blocco di spazio libero ampio e continuo per allocare l'array ridimensionato. Se l'heap viene frammentato, GC potrebbe finire per riordinare l'intero heap solo per liberare un singolo blocco di memoria adatto.
Piotr Kusmierczyk,

128
Algorithm           ArrayList   LinkedList
seek front            O(1)         O(1)
seek back             O(1)         O(1)
seek to index         O(1)         O(N)
insert at front       O(N)         O(1)
insert at back        O(1)         O(1)
insert after an item  O(N)         O(1)

Algoritmi: notazione Big-Oh

Le liste di array sono utili per scrivere-una-sola-lettura-molte o appendici, ma male per aggiungere / rimuovere dalla parte anteriore o centrale.


42
Non è possibile confrontare i valori di grande O direttamente senza pensare a fattori costanti. Per elenchi di piccole dimensioni (e la maggior parte degli elenchi sono piccoli), O (N) di ArrayList è più veloce di O (1) di LinkedList.
Porculus,

4
Non mi importa delle prestazioni di piccoli elenchi, e nemmeno il mio computer a meno che non sia usato in qualche modo in un ciclo.
Maarten Bodewes,

45
LinkedList non può davvero inserirsi nel mezzo O(1). Deve scorrere metà dell'elenco per trovare il punto di inserimento.
Thomas Ahle,

8
LinkedList: insert in middle O (1) - is WRONG! Ho scoperto che anche l'inserimento in 1/10 della dimensione di LinkedList è più lento rispetto all'inserimento di un elemento in 1/10 di posizione di un ArrayList. E ancora peggio: la fine della raccolta. l'inserimento nelle ultime posizioni (non le ultime) di ArrayList è più veloce delle ultime posizioni (non le ultime) di LinkedList
kachanov,

14
@kachanov L'inserimento in a LinkedList è O(1) se si ha un iteratore nella posizione di inserimento , cioè ListIterator.addpresumibilmente O(1)per a LinkedList.
Ha QUIT - Anony-Mousse il

107

Sì, lo so, questa è una domanda antica, ma aggiungerò i miei due centesimi:

LinkedList è quasi sempre la scelta sbagliata, dal punto di vista delle prestazioni. Ci sono alcuni algoritmi molto specifici in cui è richiesto un LinkedList, ma quelli sono molto, molto rari e l'algoritmo di solito dipenderà in modo specifico dalla capacità di LinkedList di inserire ed eliminare elementi in mezzo all'elenco relativamente rapidamente, una volta navigato lì con un ListIterator.

Esiste un caso d'uso comune in cui LinkedList supera ArrayList: quello di una coda. Tuttavia, se il tuo obiettivo è la prestazione, invece di LinkedList dovresti anche considerare di utilizzare un ArrayBlockingQueue (se puoi determinare in anticipo un limite superiore sulla dimensione della coda e puoi permetterti di allocare tutta la memoria in anticipo), o questa implementazione CircularArrayList . (Sì, è del 2001, quindi dovrai generarlo, ma ho ottenuto rapporti di prestazione comparabili a quanto riportato nell'articolo proprio ora in una recente JVM)



1
ArrayDequeè più lento rispetto a LinkedListmeno che tutte le operazioni non siano alla stessa estremità. Va bene se usato come stack ma non è una buona coda.
Elenco Jeremy

2
Non falso - almeno per l'implementazione di Oracle in jdk1.7.0_60 e nel seguente test. Ho creato un test in cui eseguo il loop per 10 milioni di volte e ho un Deque di 10 milioni di numeri casuali. All'interno del ciclo eseguo il polling di un elemento e offro un elemento costante. Sul mio computer, LinkedList è oltre 10 volte più lento di ArrayDeque e utilizza meno memoria). Il motivo è che a differenza di ArrayList, ArrayDeque mantiene un puntatore alla testa dell'array in modo che non debba spostare tutti gli elementi quando viene rimossa la testa.
Henno Vermeulen,

6
ArrayDequeè probabile che sia più veloce di Stackquando usato come stack e più veloce di LinkedListquando usato come coda.
akhil_mittal,

3
Nota che il commento di akhil_mittal è una citazione dalla ArrayDequedocumentazione.
Stuart segna il

65

È una domanda di efficienza. LinkedListè veloce per aggiungere ed eliminare elementi, ma è lento per accedere a un elemento specifico.ArrayListè veloce per accedere a un elemento specifico ma può essere lento da aggiungere a una delle estremità e soprattutto lento da eliminare nel mezzo.

Array vs ArrayList vs LinkedList vs Vector va più in profondità, così come l' elenco collegato .


54

Corretto o errato: eseguire il test localmente e decidere autonomamente!

Modifica / Rimuovi è più veloce LinkedListdi ArrayList.

ArrayList, supportato da Array, che deve essere il doppio delle dimensioni, è peggio in applicazioni di grandi volumi.

Di seguito è riportato il risultato del test unitario per ciascuna operazione. La durata è espressa in nanosecondi.


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

Ecco il codice:

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
ArrayList non deve essere raddoppiato, per essere precisi. Si prega di controllare prima le fonti.
Danubian Sailor

Va notato che il tuo esempio è difettoso ... Stai rimuovendo dalla stringa tra: 18 + [2, 12] byte ("true0false", "true500000false"), in media 25 byte, che sono le dimensioni degli elementi nel mezzo. È noto che all'aumentare della dimensione in byte dell'elemento l'elenco collegato funziona meglio, all'aumentare della dimensione dell'elenco, un array contiguo (elenco) farà meglio. Ancora più importante, stai facendo .equals () sulle stringhe - che non è un'operazione economica. Se invece utilizzassi numeri interi, penso che ci sarebbe una differenza.
Centril,

2
"... è peggio in applicazioni di grandi volumi ": questo è un malinteso. LinkedListha un sovraccarico di memoria molto maggiore perché per ogni elemento esiste un oggetto nodo con cinque campi. Su molti sistemi che fanno sovraccaricare 20 byte. Il sovraccarico di memoria medio per elemento per ArrayListè una parola e mezza, che rende 6 byte e 8 byte nel caso peggiore.
Lii,

1
Ho fatto una versione migliore del tuo benchmark qui, con risultati : le prestazioni append-on-end per l'arraylist sono artificialmente basse per la tua, perché addAll fornisce un array di archiviazione di dimensioni iniziali ESATTAMENTE, quindi il primo inserto attiva sempre un arraycopy. Inoltre, questo include cicli di riscaldamento per consentire la compilazione JIT prima che i dati vengano raccolti.
BobMcGee

4
@BillK da Java 8, è possibile utilizzare removeIf(element -> condition)ciò che si adatta, che può essere significativamente più veloce per un ArrayList, rispetto al looping e alla rimozione tramite iteratore, poiché non è necessario spostare l'intero resto per ogni singolo elemento. Se questo funziona meglio o peggio di quanto LinkedListdipende dal particolare scenario, come a LinkedListè O (1) in teoria, ma la rimozione di un solo nodo richiede diversi accessi alla memoria, che possono facilmente superare il numero necessario per la ArrayListrimozione di un numero significativo di elementi .
Holger,

50

ArrayListè essenzialmente un array. LinkedListè implementato come un doppio elenco collegato.

Il getè abbastanza chiaro. O (1) per ArrayList, poiché ArrayListconsente l'accesso casuale utilizzando l'indice. O (n) per LinkedList, perché deve prima trovare l'indice. Nota: esistono diverse versioni di adde remove.

LinkedListè più veloce in aggiungi e rimuovi, ma più lento in get. In breve, LinkedListdovrebbe essere preferito se:

  1. non esiste un numero elevato di accessi casuali all'elemento
  2. esiste un gran numero di operazioni di aggiunta / rimozione

=== ArrayList ===

  • aggiungi (E e)
    • aggiungere alla fine di ArrayList
    • richiede costi di ridimensionamento della memoria.
    • O (n) peggiore, O (1) ammortizzato
  • aggiungi (indice int, elemento E)
    • aggiungere a una posizione di indice specifica
    • richiede spostamento e possibili costi di ridimensionamento della memoria
    • Su)
  • rimuovi (indice int)
    • rimuove un elemento specificato
    • richiede spostamento e possibili costi di ridimensionamento della memoria
    • Su)
  • rimuovi (oggetto o)
    • rimuove la prima occorrenza dell'elemento specificato da questo elenco
    • è necessario prima cercare l'elemento, quindi spostare e ridurre i costi di ridimensionamento della memoria
    • Su)

=== LinkedList ===

  • aggiungi (E e)

    • aggiungere alla fine dell'elenco
    • O (1)
  • aggiungi (indice int, elemento E)

    • inserire nella posizione specificata
    • è necessario prima trovare la posizione
    • Su)
  • rimuovere()
    • rimuove il primo elemento dell'elenco
    • O (1)
  • rimuovi (indice int)
    • rimuove l'elemento con l'indice specificato
    • è necessario prima trovare l'elemento
    • Su)
  • rimuovi (oggetto o)
    • rimuove la prima occorrenza dell'elemento specificato
    • è necessario prima trovare l'elemento
    • Su)

Ecco una figura di programcreek.com ( adde removesono il primo tipo, ovvero aggiungi un elemento alla fine dell'elenco e rimuovi l'elemento nella posizione specificata nell'elenco):

inserisci qui la descrizione dell'immagine


3
"LinkedList è più veloce di aggiungi / rimuovi". Sbagliato, controllare la risposta di cui sopra stackoverflow.com/a/7507740/638670
Nerrve

49

Joshua Bloch, l'autore di LinkedList:

Qualcuno usa davvero LinkedList? L'ho scritto e non lo uso mai.

Link: https://twitter.com/joshbloch/status/583813919019573248

Mi dispiace per la risposta per non essere così istruttiva come le altre risposte, ma ho pensato che sarebbe stata la più interessante e autoesplicativa.


34

ArrayListè accessibile in modo casuale, mentre LinkedListè davvero economico per espandere e rimuovere elementi da. Nella maggior parte dei casi, ArrayListva bene.

Se non hai creato elenchi di grandi dimensioni e misurato un collo di bottiglia, probabilmente non dovrai mai preoccuparti della differenza.


15
LinkedList non è economico per aggiungere elementi. È quasi sempre più veloce aggiungere un milione di elementi a una ArrayList piuttosto che aggiungerli a una LinkedList. E la maggior parte delle liste nel codice del mondo reale non sono nemmeno lunghe un milione di elementi.
Porculus,

10
In qualsiasi momento, conosci il costo dell'aggiunta di un articolo al tuo Elenco collegato. L'ArrayList non lo fai (in generale). L'aggiunta di un singolo elemento a una ArrayList contenente un milione di elementi potrebbe richiedere molto tempo: è un'operazione O (n) più il doppio della memoria a meno che non sia stato preallocato lo spazio. L'aggiunta di un elemento a una LinkedList è O (1). La mia ultima affermazione è valida.
Dustin,

4
L'aggiunta di un singolo elemento a una ArrayList è O (1), indipendentemente dal fatto che sia 1 milione o 1 miliardo. L'aggiunta di un elemento a una LinkedList è anche O (1). "Aggiunta" significa AGGIUNTA ALLA FINE.
kachanov,

Devi aver letto l'implementazione in modo diverso da me. Nella mia esperienza, la copia di un array da 1 miliardo di elementi richiede più tempo rispetto alla copia di un array da 1 milione di elementi.
Dustin,

6
@kachanov devi fraintendere Dustin. A meno che tu non abbia dichiarato un array di 1 miliardo di articoli, dovrai eventualmente ridimensionare il tuo array, nel qual caso dovrai copiare tutti gli elementi in un nuovo array più grande, quindi a volte otterrai O (N), tuttavia con un elenco collegato sarai sempre ottenere O (1)
Stan R.

29

TL; DR grazie alla moderna architettura informatica, ArrayListsarà significativamente più efficiente per quasi tutti i possibili casi d'uso - e quindi LinkedListdovrebbe essere evitato tranne alcuni casi davvero unici ed estremi.


In teoria, LinkedList ha un O (1) per il add(E element)

Anche l'aggiunta di un elemento nel mezzo di un elenco dovrebbe essere molto efficiente.

La pratica è molto diversa, poiché LinkedList è una struttura di dati ostili della cache . Dalla performance POV - ci sono pochissimi casi in cui LinkedListpotrebbero essere più performanti di quelli compatibili con la cacheArrayList .

Ecco i risultati di un test di benchmark che inserisce elementi in posizioni casuali. Come si può vedere - la lista di array se molto più efficiente, anche se in teoria ogni inserto nel mezzo della lista richiederà "spostare" i n successivi elementi della matrice (valori più bassi sono migliori):

inserisci qui la descrizione dell'immagine

Lavorando su un hardware di generazione successiva (cache più grandi ed efficienti) - i risultati sono ancora più conclusivi:

inserisci qui la descrizione dell'immagine

LinkedList richiede molto più tempo per svolgere lo stesso lavoro. codice sorgente

Ci sono due ragioni principali per questo:

  1. Principalmente - che i nodi del LinkedListsono sparsi casualmente nella memoria. La RAM ("Random Access Memory") non è realmente casuale e i blocchi di memoria devono essere recuperati nella cache. Questa operazione richiede tempo e quando tali recuperi si verificano frequentemente, le pagine di memoria nella cache devono essere sostituite continuamente -> Mancati cache -> La cache non è efficiente. ArrayListgli elementi vengono archiviati nella memoria continua, che è esattamente ciò per cui la moderna architettura della CPU sta ottimizzando.

  2. Secondario LinkedList richiesto per trattenere i puntatori indietro / avanti, il che significa un consumo di memoria 3 volte maggiore per valore memorizzato rispetto a ArrayList.

DynamicIntArray , tra l'altro, è un'implementazione ArrayList personalizzata che tiene Int(tipo primitivo) e non Oggetti - quindi tutti i dati sono realmente archiviati in modo adiacente - quindi ancora più efficiente.

Un elemento chiave da ricordare è che il costo del recupero del blocco di memoria è più significativo del costo di accesso a una singola cella di memoria. Ecco perché il lettore 1 MB di memoria sequenziale è fino a x400 volte più veloce della lettura di questa quantità di dati da diversi blocchi di memoria:

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

Fonte: numeri di latenza che ogni programmatore dovrebbe conoscere

Solo per rendere il punto ancora più chiaro, controlla il parametro di riferimento per aggiungere elementi all'inizio dell'elenco. Questo è un caso d'uso in cui, in teoria, LinkedListdovrebbe davvero brillare e ArrayListdovrebbe presentare risultati mediocri o addirittura peggiori:

inserisci qui la descrizione dell'immagine

Nota: questo è un punto di riferimento della libreria C ++ Std, ma la mia precedente esperienza ha mostrato che i risultati C ++ e Java sono molto simili. Codice sorgente

Copiare una massa sequenziale di memoria è un'operazione ottimizzata dalle moderne CPU: cambiare la teoria e rendere, ancora, ArrayList/ Vectormolto più efficiente


Crediti: tutti i benchmark pubblicati qui sono creati da Kjell Hedström . Ancora più dati possono essere trovati sul suo blog


Non definirei una coda unica o estrema! Una quindici code è molto più semplice da implementare su un LinkedList anziché su un ArrayList. In realtà è un incubo su una ArrayList in quanto devi tenere traccia del tuo avvio, fermarti e fare la tua riallocazione, potresti anche usare un array, ma un Elenco collegato È un FIFO. Non sono sicuro dell'implementazione di Java, ma un LinkedList può eseguire O (1) sia per le operazioni di coda che di dequeue (richiede un puntatore speciale all'elemento di coda per la rimozione, che presumo abbia java ma non ho ricontrollato .)
Bill K,

24

Se il tuo codice ha add(0)e remove(0), usa a LinkedListed è più bello addFirst()e removeFirst()metodi. Altrimenti, usa ArrayList.

E, naturalmente, Guava 's ImmutableList è il tuo migliore amico.


3
Per piccoli elenchi, ArrayList.add (0) sarà sempre più veloce di LinkedList.addFirst ().
Porculus,

1
@Porculus Sento costantemente questo argomento secondo cui per le piccole liste ArrayList.add (0) sarà più veloce, questo piccolo è quanto è piccolo? 10 elementi, 10 milioni,?
garg10may

1
@ garg10may small è meno di 10.
Jesse Wilson,

@Porculus small significa meno della capacità massima dell'array interno sottostante ArrayList.
Janac Meena,

21

So che questo è un vecchio post, ma sinceramente non posso credere che nessuno abbia menzionato questo LinkedListstrumento Deque. Guarda i metodi in Deque(e Queue); se vuoi un confronto equo, prova a correre LinkedListcontro ArrayDequeed esegui un confronto funzionalità per funzionalità.


18

Ecco la notazione Big-O in entrambi ArrayListe LinkedListe anche CopyOnWrite-ArrayList:

Lista di array

get                 O(1)
add                 O(1)
contains            O(n)
next                O(1)
remove              O(n)
iterator.remove     O(n)

Lista collegata

get                 O(n)
add                 O(1)
contains            O(n)
next                O(1)
remove              O(1)
iterator.remove     O(1)

Copy-on-write-ArrayList

get                 O(1)
add                 O(n)
contains            O(n)
next                O(1)
remove              O(n)
iterator.remove     O(n)

Sulla base di questi devi decidere cosa scegliere. :)


9
>>>> ArrayList aggiungi -> O (1) <- non vero. In alcuni casi ArrayList dovrà crescere per aggiungere un altro elemento
kachanov

1
LinkedList remove non è O (1), dovrebbe cercare l'elemento da rimuovere, quindi il caso peggiore O (n) e O medio (n / 2)
garg10may

Né lo è LinkedList.add(), anche se la maggior parte delle risposte qui lo dicono.
Marchese di Lorne,

18

Confrontiamo LinkedList e ArrayList wrt sotto i parametri:

1. Attuazione

ArrayList è l'implementazione di array ridimensionabile dell'interfaccia elenco, mentre

LinkedList è l'implementazione dell'elenco con doppio collegamento dell'interfaccia dell'elenco.


2. Prestazioni

  • get (int index) o operazione di ricerca

    L'operazione get (int index) di ArrayList viene eseguita a tempo costante, ovvero O (1) mentre

    Il tempo di esecuzione dell'operazione get (int index) di LinkedList è O (n).

    Il motivo dietro ArrayList è più veloce di LinkedList è che ArrayList utilizza un sistema basato su indice per i suoi elementi in quanto utilizza internamente una struttura di dati di array, d'altra parte,

    LinkedList non fornisce un accesso basato sull'indice per i suoi elementi in quanto scorre dall'inizio o alla fine (a seconda di quale sia il più vicino) per recuperare il nodo nell'indice dell'elemento specificato.

  • operazione insert () o add (Object)

    Inserzioni in LinkedList sono generalmente veloci rispetto a ArrayList. In LinkedList l'aggiunta o l'inserimento è un'operazione O (1).

    Mentre in ArrayList , se l'array è l'intero, ovvero il caso peggiore, c'è un costo aggiuntivo per il ridimensionamento dell'array e la copia degli elementi nel nuovo array, il che rende il runtime dell'operazione di aggiunta in ArrayList O (n), altrimenti è O (1) .

  • rimuove l'operazione (int)

    L'operazione di rimozione in LinkedList è generalmente uguale a ArrayList, ovvero O (n).

    In LinkedList , ci sono due metodi di rimozione sovraccaricati. uno è remove () senza alcun parametro che rimuove la testa dell'elenco e viene eseguito a tempo costante O (1). L'altro metodo di rimozione di overload in LinkedList è remove (int) o remove (Object) che rimuove l'oggetto o int passato come parametro. Questo metodo attraversa il LinkedList fino a quando non trova l'oggetto e lo scollega dall'elenco originale. Quindi questo runtime del metodo è O (n).

    Mentre in ArrayList il metodo remove (int) prevede la copia di elementi dal vecchio array al nuovo array aggiornato, quindi il suo runtime è O (n).


3. Iteratore inverso

LinkedList può essere ripetuto nella direzione inversa usando descendingIterator () while

non esiste descendingIterator () in ArrayList , quindi è necessario scrivere il nostro codice per scorrere su ArrayList in senso inverso.


4. Capacità iniziale

Se il costruttore non è sovraccarico, allora ArrayList crea un elenco vuoto di capacità iniziale 10, mentre

LinkedList costruisce solo l'elenco vuoto senza alcuna capacità iniziale.


5. Overhead di memoria

Overhead di memoria in LinkedList è maggiore rispetto a ArrayList poiché un nodo in LinkedList deve mantenere gli indirizzi del nodo successivo e precedente. Mentre

In ArrayList ogni indice contiene solo l'oggetto reale (dati).


fonte


18

Di solito uso l'uno sull'altro in base alla complessità temporale delle operazioni che eseguirò in quel particolare elenco.

|---------------------|---------------------|--------------------|------------|
|      Operation      |     ArrayList       |     LinkedList     |   Winner   |
|---------------------|---------------------|--------------------|------------|
|     get(index)      |       O(1)          |         O(n)       | ArrayList  |
|                     |                     |  n/4 steps in avg  |            |
|---------------------|---------------------|--------------------|------------|
|      add(E)         |       O(1)          |         O(1)       | LinkedList |
|                     |---------------------|--------------------|            |
|                     | O(n) in worst case  |                    |            |
|---------------------|---------------------|--------------------|------------|
|    add(index, E)    |       O(n)          |         O(n)       | LinkedList |
|                     |     n/2 steps       |      n/4 steps     |            |
|                     |---------------------|--------------------|            |
|                     |                     |  O(1) if index = 0 |            |
|---------------------|---------------------|--------------------|------------|
|  remove(index, E)   |       O(n)          |         O(n)       | LinkedList |
|                     |---------------------|--------------------|            |
|                     |     n/2 steps       |      n/4 steps     |            |
|---------------------|---------------------|--------------------|------------|
|  Iterator.remove()  |       O(n)          |         O(1)       | LinkedList |
|  ListIterator.add() |                     |                    |            |
|---------------------|---------------------|--------------------|------------|


|--------------------------------------|-----------------------------------|
|              ArrayList               |            LinkedList             |
|--------------------------------------|-----------------------------------|
|     Allows fast read access          |   Retrieving element takes O(n)   |
|--------------------------------------|-----------------------------------|
|   Adding an element require shifting | o(1) [but traversing takes time]  |
|       all the later elements         |                                   |
|--------------------------------------|-----------------------------------|
|   To add more elements than capacity |
|    new array need to be allocated    |
|--------------------------------------|

ArrayDeque equilibra un po 'di più verso le matrici poiché inserire / rimuovere la parte anteriore / posteriore sono tutte O (1) l'unica cosa a cui l'Elenco collegato vince ancora è l'aggiunta / rimozione durante l'attraversamento (operazioni di Iteratore).
Bill K,

14

Oltre agli altri buoni argomenti di cui sopra, dovresti notare l' interfaccia degli ArrayListattrezzi RandomAccess, mentre gli LinkedListattrezzi Queue.

Quindi, in qualche modo affrontano problemi leggermente diversi, con differenze di efficienza e comportamento (vedi il loro elenco di metodi).


10

Dipende dalle operazioni che eseguirai di più nell'elenco.

ArrayListè più veloce accedere a un valore indicizzato. È molto peggio quando si inseriscono o si eliminano oggetti.

Per saperne di più, leggi qualsiasi articolo che parla della differenza tra array ed elenchi collegati.


2
Per saperne di più non leggere, basta scrivere il codice. e scoprirai che l'implementazione di ArrayList è più veloce di LinkedList nell'inserimento e nella cancellazione.
kachanov,

8

Un elenco di array è essenzialmente un array con metodi per aggiungere elementi ecc. (E invece dovresti usare un elenco generico). È una raccolta di elementi a cui è possibile accedere tramite un indicizzatore (ad esempio [0]). Implica una progressione da un elemento all'altro.

Un elenco collegato specifica una progressione da un elemento a quello successivo (Articolo a -> Articolo b). Puoi ottenere lo stesso effetto con un elenco di array, ma un elenco collegato dice assolutamente quale elemento dovrebbe seguire quello precedente.



7

Una caratteristica importante di un elenco collegato (che non ho letto in un'altra risposta) è la concatenazione di due elenchi. Con un array questo è O (n) (+ sovraccarico di alcune riallocazioni) con un elenco collegato questo è solo O (1) o O (2) ;-)

Importante : per Java LinkedListnon è vero! Vedi Esiste un metodo rapido di concat per l'elenco collegato in Java?


2
Come va? Ciò può essere vero con le strutture dati dell'elenco collegato ma non con un oggetto LinkList Java. Non puoi semplicemente puntare a nextda un elenco al primo nodo nel secondo elenco. L'unico modo è utilizzare addAll()che aggiunge elementi in sequenza, anche se è meglio che passare in rassegna e chiamare add()ogni elemento. Per fare questo velocemente in O (1) avresti bisogno di una classe di composizione (come org.apache.commons.collections.collection.CompositeCollection) ma funzionerebbe per qualsiasi tipo di Elenco / Collezione.
Kevin Brock,

sì vero. Ho modificato la risposta di conseguenza. ma vedi questa risposta per "come" farlo con LinkedList: stackoverflow.com/questions/2494031/…
Karussell

7

ArrayList e LinkedList hanno i loro pro e contro.

ArrayList utilizza un indirizzo di memoria contiguo rispetto a LinkedList che utilizza i puntatori verso il nodo successivo. Pertanto, quando si desidera cercare un elemento in un array è più veloce che fare iterazioni con LinkedList.

D'altra parte, l'inserimento e la cancellazione in una LinkedList sono molto più facili perché devi solo cambiare i puntatori mentre una ArrayList implica l'uso dell'operazione shift per qualsiasi inserimento o cancellazione.

Se hai frequenti operazioni di recupero nella tua app usa una ArrayList. Se si hanno frequenti inserimenti ed eliminazioni, utilizzare una LinkedList.


6

Ho letto le risposte, ma esiste uno scenario in cui utilizzo sempre un elenco di collegamenti su un elenco di array che desidero condividere per ascoltare le opinioni:

Ogni volta che ho avuto un metodo che restituisce un elenco di dati ottenuti da un DB, utilizzo sempre un LinkedList.

La mia logica era che, poiché è impossibile sapere esattamente quanti risultati sto ottenendo, non ci sarà spreco di memoria (come in ArrayList con la differenza tra la capacità e il numero effettivo di elementi) e non ci sarebbe tempo perso a provare duplicare la capacità.

Per quanto riguarda una ArrayList, sono d'accordo che almeno dovresti sempre usare il costruttore con la capacità iniziale, per ridurre al minimo la duplicazione degli array.


5

ArrayListe LinkedListsia gli attrezzi List interface che i loro metodi e risultati sono quasi identici. Tuttavia ci sono alcune differenze tra loro che rendono uno migliore rispetto all'altro a seconda delle esigenze.

ArrayList Vs LinkedList

1) l' Search: ArrayListoperazione di ricerca è piuttosto veloce rispetto all'operazione di LinkedListricerca. get(int index)in ArrayListdà la prestazione di O(1)mentre la LinkedListprestazione è O(n).

Reason: ArrayListmantiene il sistema basato sull'indice per i suoi elementi in quanto utilizza implicitamente la struttura dei dati dell'array che lo rende più veloce per la ricerca di un elemento nell'elenco. Dall'altro lato LinkedListimplementa un elenco doppiamente collegato che richiede l'attraversamento di tutti gli elementi per la ricerca di un elemento.

2) L' Deletion: LinkedListoperazione di rimozione fornisce O(1)prestazioni mentre ArrayListfornisce prestazioni variabili: O(n)nel peggiore dei casi (durante la rimozione del primo elemento) e O(1)nella migliore delle ipotesi (durante la rimozione dell'ultimo elemento).

Conclusione: l'eliminazione dell'elemento LinkedList è più rapida rispetto ad ArrayList.

Motivo: ogni elemento di LinkedList mantiene due puntatori (indirizzi) che puntano a entrambi gli elementi vicini nell'elenco. Quindi la rimozione richiede solo la modifica della posizione del puntatore nei due nodi (elementi) vicini del nodo che verrà rimosso. Mentre in ArrayList è necessario spostare tutti gli elementi per riempire lo spazio creato dall'elemento rimosso.

3) il Inserts Performance: LinkedListmetodo add dà O(1)prestazioni mentre ArrayListO(n)nel peggiore dei casi. Il motivo è lo stesso spiegato per la rimozione.

4) Memory Overhead: ArrayListmantiene gli indici e i dati degli elementi mentre LinkedListmantiene i dati degli elementi e due puntatori per i nodi vicini

quindi il consumo di memoria è relativamente alto in LinkedList relativamente.

Esistono alcune somiglianze tra queste classi che sono le seguenti:

  • Sia ArrayList che LinkedList sono implementazioni dell'interfaccia Elenco.
  • Entrambi mantengono l'ordine di inserimento degli elementi, il che significa che durante la visualizzazione degli elementi ArrayList e LinkedList il set di risultati avrebbe lo stesso ordine in cui gli elementi sono stati inseriti nell'elenco.
  • Entrambe queste classi non sono sincronizzate e possono essere sincronizzate esplicitamente usando il metodo Collections.synchronizedList.
  • Le iteratore listIteratorrestituite da queste classi sono fail-fast(se l'elenco viene modificato strutturalmente in qualsiasi momento dopo la creazione dell'iteratore, in qualsiasi modo tranne che attraverso i iterator’spropri metodi di rimozione o aggiunta, l'iteratore sarà throwa ConcurrentModificationException).

Quando utilizzare LinkedList e quando utilizzare ArrayList?

  • Come spiegato sopra le operazioni di inserimento e rimuovere offrono buone prestazioni (O(1))in LinkedListconfronto aArrayList(O(n)) .

    Quindi, se c'è un requisito di frequente aggiunta e cancellazione nell'applicazione, allora LinkedList è la scelta migliore.

  • Le operazioni di ricerca ( get method) sono veloci Arraylist (O(1))ma non attiveLinkedList (O(n))

    quindi Se ci sono meno operazioni di aggiunta e rimozione e più requisiti per le operazioni di ricerca, ArrayList sarebbe la soluzione migliore.


5

L'operazione get (i) in ArrayList è più veloce di LinkedList, perché:
ArrayList: implementazione di array ridimensionabili dell'interfaccia Elenco
LinkedList: doppiamente collegati delle interfacce List e Deque

Le operazioni che indicizzano nell'elenco attraverseranno l'elenco dall'inizio o dalla fine, a seconda di quale sia il più vicino all'indice specificato.


5

1) Struttura dei dati sottostante

La prima differenza tra ArrayList e LinkedList deriva dal fatto che ArrayList è supportato da Array mentre LinkedList è supportato da LinkedList. Ciò comporterà ulteriori differenze nelle prestazioni.

2) LinkedList implementa Deque

Un'altra differenza tra ArrayList e LinkedList è che, oltre all'interfaccia List, LinkedList implementa anche l'interfaccia Deque, che fornisce le prime operazioni di first out per add () e poll () e diverse altre funzioni Deque. 3) Aggiunta di elementi in ArrayList L'aggiunta di elementi in ArrayList è un'operazione O (1) se non attiva la ridimensionamento di matrice, nel qual caso diventa O (log (n)), D'altra parte, aggiungendo un elemento in LinkedList è un'operazione O (1), in quanto non richiede alcuna navigazione.

4) Rimozione di un elemento da una posizione

Per rimuovere un elemento da un determinato indice, ad es. Chiamando remove (indice), ArrayList esegue un'operazione di copia che lo avvicina a O (n) mentre LinkedList deve attraversare quel punto che lo rende anche O (n / 2) , poiché può attraversare da entrambe le direzioni in base alla prossimità.

5) Iterazione su ArrayList o LinkedList

Iterazione è l'operazione O (n) sia per LinkedList che per ArrayList in cui n è un numero di un elemento.

6) Recupero dell'elemento da una posizione

L'operazione get (indice) è O (1) in ArrayList mentre O (n / 2) in LinkedList, poiché deve attraversare fino a quella voce. Tuttavia, nella notazione O grande O (n / 2) è solo O (n) perché qui ignoriamo le costanti.

7) Memoria

LinkedList utilizza un oggetto wrapper, Entry, che è una classe nidificata statica per l'archiviazione dei dati e due nodi successivi e precedenti, mentre ArrayList archivia semplicemente i dati in array.

Pertanto, il requisito di memoria sembra inferiore nel caso di ArrayList rispetto a LinkedList, tranne nel caso in cui Array esegua l'operazione di ridimensionamento quando copia il contenuto da un array all'altro.

Se la matrice è abbastanza grande, a quel punto potrebbe essere necessaria molta memoria e attivare la Garbage Collection, che può rallentare i tempi di risposta.

Da tutte le differenze di cui sopra tra ArrayList vs LinkedList, sembra che ArrayList sia la scelta migliore di LinkedList in quasi tutti i casi, tranne quando si esegue un'operazione add () frequente rispetto a remove () o get ().

È più semplice modificare un elenco collegato rispetto a ArrayList, soprattutto se si stanno aggiungendo o rimuovendo elementi dall'inizio o alla fine perché l'elenco collegato mantiene internamente i riferimenti di tali posizioni e sono accessibili in O (1).

In altre parole, non è necessario attraversare l'elenco collegato per raggiungere la posizione in cui si desidera aggiungere elementi, in tal caso l'addizione diventa un'operazione O (n). Ad esempio, inserendo o eliminando un elemento nel mezzo di un elenco collegato.

A mio avviso, utilizzare ArrayList su LinkedList per la maggior parte dello scopo pratico in Java.


1
Penso che questa sia la risposta dichiarata migliore dell'intero gruppo qui. È preciso e informativo. Suggerirei di cambiare l'ultima riga - alla fine aggiungere "a parte le code" che sono strutture molto importanti che non hanno alcun senso per un elenco collegato.
Bill K,

3

Uno dei test che ho visto qui conduce il test solo una volta. Ma quello che ho notato è che è necessario eseguire questi test molte volte e alla fine i loro tempi convergeranno. Fondamentalmente la JVM deve riscaldarsi. Per il mio caso d'uso specifico ho dovuto aggiungere / rimuovere elementi in un elenco che cresce fino a circa 500 articoli. Nei miei test LinkedListsono uscito più velocemente, LinkedListarrivando a circa 50.000 NS e ArrayListarrivando a circa 90.000 NS ... dai o dai. Vedi il codice qui sotto.

public static void main(String[] args) {
    List<Long> times = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        times.add(doIt());
    }
    System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}

static long doIt() {
    long start = System.nanoTime();
    List<Object> list = new LinkedList<>();
    //uncomment line below to test with ArrayList
    //list = new ArrayList<>();
    for (int i = 0; i < 500; i++) {
        list.add(i);
    }

    Iterator it = list.iterator();
    while (it.hasNext()) {
        it.next();
        it.remove();
    }
    long end = System.nanoTime();
    long diff = end - start;
    //uncomment to see the JVM warmup and get faster for the first few iterations
    //System.out.println(diff)
    return diff;
}

2

Sia remove () che insert () hanno un'efficienza di runtime di O (n) sia per ArrayLists che per LinkedLists. Tuttavia, la ragione dietro il tempo di elaborazione lineare deriva da due ragioni molto diverse:

In una ArrayList, si arriva all'elemento in O (1), ma in realtà la rimozione o l'inserimento di qualcosa lo rende O (n) perché tutti i seguenti elementi devono essere cambiati.

In una LinkedList, ci vuole O (n) per arrivare effettivamente all'elemento desiderato, perché dobbiamo iniziare dall'inizio fino a raggiungere l'indice desiderato. In realtà la rimozione o l'inserimento è costante, perché dobbiamo cambiare solo 1 riferimento per remove () e 2 riferimenti per insert ().

Quale dei due è più veloce per l'inserimento e la rimozione dipende da dove accade. Se siamo più vicini all'inizio, la LinkedList sarà più veloce, perché dobbiamo esaminare relativamente pochi elementi. Se siamo più vicini alla fine, un ArrayList sarà più veloce, perché ci arriveremo in tempo costante e dovremo solo cambiare i pochi elementi rimanenti che lo seguono. Se fatto esattamente nel mezzo, il LinkedList sarà più veloce perché passare attraverso n elementi è più veloce dello spostamento di n valori.

Bonus: Mentre non c'è modo di rendere questi due metodi O (1) per una ArrayList, in realtà c'è un modo per farlo in LinkedLists. Supponiamo di voler esaminare l'intero Elenco rimuovendo e inserendo elementi lungo il nostro cammino. Di solito, inizieresti dall'inizio per ogni elemento usando il LinkedList, potremmo anche "salvare" l'elemento corrente su cui stiamo lavorando con un Iteratore. Con l'aiuto di Iterator, otteniamo un'efficienza O (1) per remove () e insert () quando lavoriamo in un LinkedList. Rendendolo l'unico vantaggio in termini di prestazioni, sono consapevole di dove un LinkedList è sempre meglio di un ArrayList.


1

ArrayList estende AbstractList e implementa l'interfaccia elenco. ArrayList è un array dinamico.
Si può dire che è stato sostanzialmente creato per ovviare agli inconvenienti degli array.

La classe LinkedList estende AbstractSequentialList e implementa l'interfaccia List, Deque e Queue.
Le prestazioni
arraylist.get()sono O (1) mentre linkedlist.get()O (n)
arraylist.add()è O (1) ed linkedlist.add()è 0 (1)
arraylist.contains()è O (n) ed linkedlist.contains()è O (n)
arraylist.next()è O (1) ed linkedlist.next()è O (1)
arraylist.remove()è O (n) mentre linkedlist.remove()è O (1)
In arraylist
iterator.remove()è O (n)
mentre In linklist
iterator.remove()è O (1)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.