Por que ArrayDeque é melhor que LinkedList

160

Estou tentando entender por que o ArrayDeque do Java é melhor que o LinkedList do Java, pois ambos implementam a interface Deque.

Quase não vejo alguém usando ArrayDeque em seu código. Se alguém esclarecer como o ArrayDeque é implementado, seria útil.

Se eu entender, ficarei mais confiante em usá-lo. Eu não conseguia entender claramente a implementação do JDK quanto à maneira como ele gerencia as referências de cabeçalho e cauda.

Cruel
fonte
5
Veja a resposta nesta pergunta que fiz há alguns dias: stackoverflow.com/questions/6129805/…
Renato Dinhani 28/11
1
Na pasta em que você tem o seu jdk instalado, há um arquivo src.zip. É o arquivo com o código fonte das classes java. Eu recomendo vivamente estudar a estrutura e as classes internas dessas classes para entender melhor como as classes java funcionam.

Respostas:

155

Estruturas vinculadas são possivelmente a pior estrutura para iterar com uma falta de cache em cada elemento. Além disso, eles consomem muito mais memória.

Se você precisar adicionar / remover ambas as extremidades, o ArrayDeque é significativamente melhor que uma lista vinculada. O acesso aleatório a cada elemento também é O (1) para uma fila cíclica.

A única operação melhor de uma lista vinculada é remover o elemento atual durante a iteração.

bestsss
fonte
56
Outra diferença a ter em mente: o LinkedList suporta elementos nulos, enquanto o ArrayDeque não.
10753 Luke Usherwood
14
Outra pequena desvantagem (para aplicativos em tempo real) é que, em uma operação push / add, é necessário um pouco mais quando a matriz interna do ArrayDeque está cheia, pois é necessário dobrar seu tamanho e copiar todos os dados.
Andrei I
4
@AndreiI, esse é apenas um lado da história. Mesmo se você excluir os custos de iteração para o aplicativo em tempo real e a capacidade de pré-alocar a capacidade necessária, o GC poderá precisar iterar o LinkedList inteiro. Basicamente, você está movendo os custos (que são mais altos para inicializar) no GC.
bestsss 19/05/19
3
@DavidT. b / c envolve custos de GC do nó liberado, atribuir a cabeça também pode exigir marcação de cartão (para o GC novamente, se o LinkedList já estiver no gen tenured) ... e isso está além do indireto extra (cache- miss) para retornar o elemento e vincular novamente.
bestsss
1
Além disso, LinkedListimplementa Listenquanto ArrayDequenão. Isso significa que LinkedListexistem métodos como indexOfou remove(int)enquanto ArrayDequenão. Às vezes pode ser importante.
ZhekaKozlov 02/09/2015
60

Acredito que o principal gargalo de desempenho LinkedListé o fato de que, sempre que você avança para qualquer extremidade do deque, nos bastidores, a implementação aloca um novo nó de lista vinculado, que envolve essencialmente JVM / OS, e isso é caro. Além disso, sempre que você salta de qualquer extremidade, os nós internos LinkedListse tornam elegíveis para a coleta de lixo e isso é mais trabalhoso nos bastidores. Além disso, como os nós da lista vinculada são alocados aqui e ali, o uso do cache da CPU não trará muitos benefícios.

Se puder ser interessante, tenho uma prova de que adicionar (anexar) um elemento ArrayListou ser ArrayDequeexecutado em tempo constante amortizado; consulte isso .

coderodde
fonte
26

ArrayDeque é novo no Java 6, e é por isso que muitos códigos (especialmente projetos que tentam ser compatíveis com versões anteriores do Java) não o usam.

É "melhor" em alguns casos, porque você não está alocando um nó para cada item a ser inserido; todos os elementos são armazenados em uma matriz gigante, que é redimensionada se ficar cheia.

Chris Jester-Young
fonte
17

Todas as pessoas que criticam a LinkedList, pensam em todos os outros usuários que usam ListJava, provavelmente usam ArrayListe na LinkedListmaioria das vezes porque estiveram antes do Java 6 e porque essas são as que estão sendo ensinadas como um começo na maioria dos livros.

Mas, isso não significa, eu cegamente tomar LinkedList's ou ArrayDeques lado'. Se você quiser saber, dê uma olhada no benchmark abaixo feito por Brian .

A configuração do teste considera:

  • Cada objeto de teste é uma cadeia de 500 caracteres. Cada String é um objeto diferente na memória.
  • O tamanho da matriz de teste será variado durante os testes.
  • Para cada combinação de tamanho de matriz / implementação de fila, 100 testes são executados e o tempo médio por teste é calculado.
  • Cada teste consiste em preencher cada fila com todos os objetos e removê-los todos.
  • Meça o tempo em termos de milissegundos.

Resultado do teste:

  • Abaixo de 10.000 elementos, os testes LinkedList e ArrayDeque tiveram uma média de um nível inferior a 1 ms.
  • À medida que os conjuntos de dados aumentam, as diferenças entre o tempo médio de teste ArrayDeque e LinkedList aumentam.
  • No tamanho do teste de 9.900.000 elementos, a abordagem LinkedList demorou ~ 165% a mais que a abordagem ArrayDeque.

Gráfico:

insira a descrição da imagem aqui

Leve embora:

  • Se o seu requisito estiver armazenando 100 ou 200 elementos, não faria muita diferença usar qualquer uma das filas.
  • No entanto, se você estiver desenvolvendo em dispositivos móveis, convém usar um ArrayListou ArrayDequecom um bom palpite sobre a capacidade máxima que a lista pode exigir devido a uma restrição restrita de memória.
  • Existe um monte de código, escrito usando um LinkedListprocedimento tão cuidadoso ao decidir usar um, ArrayDequeespecialmente porque ele não implementa a Listinterface (acho que esse é o motivo). Pode ser que sua base de código converse com a interface da lista extensivamente, provavelmente e você decida usar um ArrayDeque. Usá-lo para implementações internas pode ser uma boa ideia ...
Manish Kumar Sharma
fonte
Como esse benchmark capturaria o tempo do GC causado pelo lixo da lista vinculada?
0xbe5077ed
3

ArrayDeque e LinkedList estão implementando a interface Deque , mas a implementação é diferente.

Principais diferenças:

  1. A classe ArrayDeque é a implementação redimensionável da interface Deque e a classe LinkedList é a implementação da lista

  2. Elementos NULL podem ser adicionados ao LinkedList, mas não no ArrayDeque

  3. ArrayDeque é mais eficiente que o LinkedList para adicionar e remover operações nas duas extremidades e a implementação do LinkedList é eficiente para remover o elemento atual durante a iteração

  4. A implementação LinkedList consome mais memória que o ArrayDeque

Portanto, se você não precisa suportar elementos NULL e procurar menos memória e eficiência de adicionar / remover elementos nas duas extremidades, o ArrayDeque é o melhor

Consulte a documentação para mais detalhes.

Ravindra babu
fonte
13
"Na lista vinculada, será necessário O (N) para encontrar o último elemento." não é exatamente verdade. O LinkedList é implementado como uma lista duplamente vinculada, para que você não precise percorrer a lista para obter o último elemento ( header.previous.element). A alegação "a eficiência da memória" também pode ser contestada desde a matriz de suporte é sempre redimensionada para a próxima potência de 2.
Clément MATHIEU
5
"Vai levar O (N) para encontrar o último elemento" está ERRADO. A lista vinculada mantém uma referência ao último nó e o LinkedList.descendingIterator () obtém esse nó. Portanto, obtemos desempenho O (1). Veja: coffeeorientedprogramming.wordpress.com/2018/04/23/... (downvoting assim).
21818 Leo Ufimtsev
1
Quando você usa um Iteratorpara acessar o último elemento, a operação é O (N) para ambas as classes. Quando você usa a Dequeinterface comum , o acesso ao último elemento é O (1) para ambas as classes. Independentemente de qual ponto de vista você adotar, atribuir O (1) a ArrayDequee O (N) ao LinkedListmesmo tempo está errado.
Holger
Todas as suas sugestões foram recebidas e corrigidas
Ravindra babu 25/04
1

embora ArrayDeque<E>e LinkedList<E>tenham ambos implementado a Deque<E>interface, mas o ArrayDeque usa basicamente o array de objetos E[]para manter os elementos dentro do objeto, portanto geralmente usa o índice para localizar os elementos de cabeçalho e cauda.

Em uma palavra, funciona apenas como Deque (com todo o método de Deque), no entanto, usa a estrutura de dados da matriz. Quanto a qual é o melhor, depende de como e onde você os usa.

rekinyz
fonte
-1

Nem sempre é esse o caso.

Por exemplo, no caso abaixo linkedlist, o desempenho é melhor que o ArrayDequedo leetcode 103.

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

A complexidade de tempo para ArrayDeque para acessar um elemento é O (1) e para LinkList é O (N) para acessar o último elemento. O ArrayDeque não é seguro para threads, sendo necessária a sincronização manual para que você possa acessá-lo através de vários threads e para que sejam mais rápidos.

Piyush Yawalkar
fonte
3
Se você está se referindo ao LinkedListJava Collection, ele é duplamente vinculado e tem acesso rápido à cabeça e à cauda, ​​portanto, o acesso ao último elemento também leva O (1).
Maurice
O acesso ao último elemento no LinkedList não é O (N). Se você usar descendingIterator (), ele será executado em O (1). Consulte coffeeorientedprogramming.wordpress.com/2018/04/23/… (portanto, voto negativo).
21818 Leo Ufimtsev
1
Ambas as classes não são seguras para threads. E não há conexão entre o início da frase "a sincronização manual é necessária" e o final "eles são mais rápidos".
Holger