Perché ArrayDeque é meglio di LinkedList


161

Sto cercando di capire perché ArrayDeque di Java è migliore di LinkedList di Java poiché entrambi implementano l'interfaccia Deque.

Non vedo quasi nessuno usare ArrayDeque nel suo codice. Se qualcuno fa più luce sull'implementazione di ArrayDeque, sarebbe utile.

Se lo capisco, sarò più sicuro nell'usarlo. Non riuscivo a capire chiaramente l'implementazione di JDK sul modo in cui gestisce i riferimenti di testa e coda.


5
Guardate la risposta in questa domanda che ho fatto giorni fa: stackoverflow.com/questions/6129805/...
Renato Dinhani

1
Nella cartella in cui hai installato jdk, c'è un file src.zip. È l'archivio con il codice sorgente delle classi java. Consiglio vivamente di studiare la struttura di queste classi e gli interni per capire meglio come funzionano le classi java.

Risposte:


155

Le strutture collegate sono probabilmente la struttura peggiore da ripetere con una cache mancata su ogni elemento. Inoltre consumano molta più memoria.

Se è necessario aggiungere / rimuovere entrambe le estremità, ArrayDeque è significativamente migliore di un elenco collegato. L'accesso casuale ogni elemento è anche O (1) per una coda ciclica.

L'unica migliore operazione di un elenco collegato è la rimozione dell'elemento corrente durante l'iterazione.


57
Un'altra differenza da tenere a mente: LinkedList supporta elementi null, mentre ArrayDeque no.
Luke Usherwood,

15
Un altro piccolo svantaggio (per applicazioni in tempo reale) è che in un'operazione push / add ci vuole un po 'di più quando l'array interno di ArrayDeque è pieno, poiché deve raddoppiare le sue dimensioni e copiare tutti i dati.
Andrei I,

4
@AndreiI, questo è solo un lato della storia. Anche se si escludono i costi di iterazione per l'applicazione in tempo reale e la capacità di preallocare la capacità necessaria, il GC potrebbe dover ripetere l'intera lista collegata. Fondamentalmente stai spostando i costi (che sono più alti per l'avvio) nel GC.
bestsss

3
@DavidT. b / c coinvolge i costi GC del nodo liberato, l'assegnazione della testa potrebbe richiedere anche la marcatura della carta (per il GC di nuovo, se la LinkedList è già nella generazione posseduta) ... e questo è in cima alla indiretta extra (cache- miss) per restituire l'elemento e ricollegare.
bestsss

1
Inoltre, LinkedListimplementa Listmentre ArrayDequenon lo fa. Ciò significa che LinkedListhanno metodi come indexOfo remove(int)while ArrayDequeno. A volte può essere importante.
ZhekaKozlov,

60

Credo che il principale collo di bottiglia nelle prestazioni LinkedListsia il fatto che ogni volta che spingi a qualsiasi estremità del deque, dietro la scena l'implementazione alloca un nuovo nodo di elenco collegato, che coinvolge essenzialmente JVM / OS, ed è costoso. Inoltre, ogni volta che fai pop da qualsiasi estremità, i nodi interni LinkedListdiventano idonei per la garbage collection e questo è più lavoro dietro la scena. Inoltre, poiché i nodi dell'elenco collegato sono allocati qua e là, l'uso della cache della CPU non fornirà molti vantaggi.

Se potrebbe essere interessante, ho la prova che l'aggiunta (aggiunta) di un elemento ArrayListo l' ArrayDequeesecuzione avviene in un tempo costante ammortizzato; fare riferimento a questo .


26

ArrayDeque è nuovo con Java 6, motivo per cui un sacco di codice (in particolare i progetti che cercano di essere compatibili con le versioni precedenti di Java) non lo usano.

È "migliore" in alcuni casi perché non stai allocando un nodo per ogni elemento da inserire; invece tutti gli elementi sono memorizzati in un array gigante, che viene ridimensionato se si riempie.


17

Tutte le persone che criticano un LinkedList, pensano a ogni altro ragazzo che ha usato Listin Java probabilmente lo usa ArrayListe la LinkedListmaggior parte delle volte perché sono stati prima di Java 6 e perché quelli sono quelli insegnati come un inizio nella maggior parte dei libri.

Ma, ciò non significa che, avrei preso alla cieca LinkedList's o ArrayDequelato' s. Se vuoi sapere, dai un'occhiata al benchmark di seguito fatto da Brian .

L'impostazione del test considera:

  • Ogni oggetto test è una stringa di 500 caratteri. Ogni stringa è un oggetto diverso in memoria.
  • La dimensione dell'array di test verrà modificata durante i test.
  • Per ogni combinazione dimensione array / coda-implementazione, vengono eseguiti 100 test e viene calcolato il tempo medio per test.
  • Ogni test consiste nel riempire ogni coda con tutti gli oggetti, quindi rimuoverli tutti.
  • Misura il tempo in termini di millisecondi.

Risultato del test:

  • Al di sotto di 10.000 elementi, i test di LinkedList e ArrayDeque sono stati in media a un livello inferiore a 1 ms.
  • Man mano che le serie di dati aumentano, le differenze tra il tempo medio di test di ArrayDeque e LinkedList aumentano.
  • Alla dimensione di test di 9.900.000 di elementi, l'approccio LinkedList ha impiegato circa il 165% in più rispetto all'approccio ArrayDeque.

Grafico:

inserisci qui la descrizione dell'immagine

Porta via:

  • Se il tuo requisito è di memorizzare 100 o 200 elementi, non farebbe molta differenza usando una delle code.
  • Tuttavia, se si sta sviluppando su dispositivi mobili, è possibile che si desideri utilizzare una ArrayListo ArrayDequecon una buona ipotesi di capacità massima che potrebbe essere richiesta dall'elenco a causa di un vincolo di memoria rigoroso.
  • Esiste un sacco di codice, scritto usando un LinkedListpasso così accurato quando si decide di usare un ArrayDequesoprattutto perché NON implementa l' Listinterfaccia (penso che sia abbastanza grande). È possibile che il tuo codebase parli ampiamente con l'interfaccia List, molto probabilmente e decidi di saltare con un ArrayDeque. Usarlo per implementazioni interne potrebbe essere una buona idea ...

In che modo questo benchmark catturerebbe il tempo GC causato dalla spazzatura dell'elenco collegato?
0xbe5077ed

3

ArrayDeque e LinkedList stanno implementando Deque interfaccia ma l'implementazione è diversa.

Differenze chiave:

  1. La classe ArrayDeque è l'implementazione dell'array ridimensionabile dell'interfaccia Deque e la classe LinkedList è l'implementazione dell'elenco

  2. Gli elementi NULL possono essere aggiunti a LinkedList ma non in ArrayDeque

  3. ArrayDeque è più efficiente di LinkedList per aggiungere e rimuovere operazioni su entrambi i lati e l'implementazione di LinkedList è efficiente per rimuovere l'elemento corrente durante l'iterazione

  4. L' implementazione di LinkedList consuma più memoria di ArrayDeque

Quindi, se non devi supportare elementi NULL && alla ricerca di meno memoria && efficienza di aggiunta / rimozione di elementi su entrambe le estremità, ArrayDeque è il migliore

Fare riferimento alla documentazione per maggiori dettagli.


13
"Nell'elenco collegato, ci vorrà O (N) per trovare l'ultimo elemento." non è esattamente vero. LinkedList è implementato come un elenco doppiamente collegato in modo da non dover attraversare l'elenco per ottenere l'ultimo elemento ( header.previous.element). L'affermazione "efficienza della memoria" può anche essere contestata poiché l'array di supporto viene sempre ridimensionato alla potenza successiva di 2.
Clément MATHIEU,

5
"Ci vorrà O (N) per trovare l'ultimo elemento" è SBAGLIATO. Elenco collegato mantiene un riferimento all'ultimo nodo e LinkedList.descendingIterator () ottiene quel nodo. Quindi otteniamo prestazioni O (1). Vedi: coffeeorientedprogramming.wordpress.com/2018/04/23/… (quindi downvoting).
Leo Ufimtsev,

1
Quando si utilizza un Iteratorper accedere all'ultimo elemento, l'operazione è O (N) per entrambe le classi. Quando si utilizza l' Dequeinterfaccia comune , l'accesso all'ultimo elemento è O (1) per entrambe le classi. Indipendentemente da quale punto di vista prendi, attribuire O (1) a ArrayDequee O (N) LinkedListallo stesso tempo, è sbagliato.
Holger

Tutti i tuoi suggerimenti sono stati presi e corretti il ​​contenuto
Ravindra babu

1

sebbene ArrayDeque<E>e LinkedList<E>hanno entrambe implementato Deque<E>interfaccia, ma l'ArrayDeque utilizza fondamentalmente matrice Object E[]per mantenere gli elementi all'interno del suo oggetto, in modo che utilizza generalmente indice per posizionare gli elementi di testa e di coda.

In una parola, funziona come Deque (con tutto il metodo Deque), tuttavia utilizza la struttura dei dati dell'array. Per quanto riguarda quale è meglio, dipende da come e dove li usi.


-1

Non è sempre così.

Ad esempio, nel caso seguente linkedlistha prestazioni migliori rispetto ArrayDequeal codice leet 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;
    }
}

-5

La complessità temporale per ArrayDeque per l'accesso a un elemento è O (1) e quella per LinkList è O (N) per accedere all'ultimo elemento. ArrayDeque non è sicuro per i thread, quindi è necessaria la sincronizzazione manuale in modo che sia possibile accedervi attraverso più thread e in modo che siano più veloci.


3
Se ti riferisci a LinkedListJava Collection, è doppiamente collegato e ha un rapido accesso alla testa e alla coda, quindi anche l'accesso all'ultimo elemento prende O (1).
Maurice,

L'accesso all'ultimo elemento in LinkedList non è O (N). Se si utilizza descendingIterator (), viene eseguito in O (1). Vedi coffeeorientedprogramming.wordpress.com/2018/04/23/… (quindi downvote).
Leo Ufimtsev,

1
Entrambe le classi non sono thread-safe. E non esiste alcuna connessione tra l'inizio della frase "è necessaria la sincronizzazione manuale" e la sua fine "sono più veloci".
Holger
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.