¿Por qué es ArrayDeque mejor que LinkedList?

161

Estoy tratando de entender por qué ArrayDeque de Java es mejor que LinkedList de Java, ya que ambos implementan la interfaz Deque.

Apenas veo a alguien usando ArrayDeque en su código. Si alguien arroja más luz sobre cómo se implementa ArrayDeque, sería útil.

Si lo entiendo, estaré más seguro de usarlo. No podía entender claramente la implementación de JDK en cuanto a la forma en que maneja las referencias de cabeza y cola.

Cruel
fuente
55
Mira la respuesta en esta pregunta que hice hace días: stackoverflow.com/questions/6129805/…
Renato Dinhani
1
En la carpeta, donde tiene instalado su jdk, hay un archivo src.zip. Es el archivo con el código fuente de las clases de Java. Recomiendo estudiar la estructura de estas clases y las partes internas para comprender mejor cómo funcionan las clases de Java.

Respuestas:

155

Las estructuras vinculadas son posiblemente la peor estructura para iterar con una falta de caché en cada elemento. Además, consumen mucha más memoria.

Si necesita agregar / quitar ambos extremos, ArrayDeque es significativamente mejor que una lista vinculada. El acceso aleatorio de cada elemento también es O (1) para una cola cíclica.

La única mejor operación de una lista vinculada es eliminar el elemento actual durante la iteración.

bestsss
fuente
57
Otra diferencia a tener en cuenta: LinkedList admite elementos nulos, mientras que ArrayDeque no.
Luke Usherwood
15
También otra pequeña desventaja (para aplicaciones en tiempo real) es que en una operación push / add se necesita un poco más cuando la matriz interna de ArrayDeque está llena, ya que tiene que duplicar su tamaño y copiar todos los datos.
Andrei I
44
@ Andrei, este es solo un lado de la historia. Incluso si excluye los costos de iteración para la aplicación en tiempo real y la capacidad de preasignar la capacidad necesaria, el GC puede necesitar iterar toda la LinkedList. Básicamente, está trasladando los costos (que son más altos para arrancar) al GC.
Bestsss
3
@DavidT. b / c implica costos de GC del nodo liberado, la asignación de la cabeza también puede requerir el marcado de la tarjeta (para el GC nuevamente, si LinkedList ya está en la generación de tenencia) ... y eso es además de la indirección adicional (caché- miss) para devolver el elemento y volver a vincular.
bestsss
1
Además, se LinkedListimplementa Listmientras ArrayDequeque no. Esto significa que LinkedListtienen métodos como indexOfo remove(int)mientras que ArrayDequeno. Puede ser importante a veces.
ZhekaKozlov
60

Creo que el principal cuello de botella en el rendimiento LinkedListes el hecho de que cada vez que avanza hacia cualquier extremo de la deque, detrás de escena, la implementación asigna un nuevo nodo de lista vinculada, que esencialmente involucra JVM / OS, y eso es costoso. Además, cada vez que aparece desde cualquier extremo, los nodos internos se LinkedListvuelven elegibles para la recolección de basura y eso es más trabajo detrás de escena. Además, dado que los nodos de la lista vinculada se asignan aquí y allá, el uso de la memoria caché de la CPU no proporcionará muchos beneficios.

Si puede ser de interés, tengo una prueba de que agregar (agregar) un elemento ArrayListo ArrayDequeejecuta en tiempo constante amortizado; refiérase a esto .

coderodde
fuente
26

ArrayDeque es nuevo con Java 6, por lo que una gran cantidad de código (especialmente proyectos que intentan ser compatibles con versiones anteriores de Java) no lo usan.

Es "mejor" en algunos casos porque no está asignando un nodo para cada elemento para insertar; en cambio, todos los elementos se almacenan en una matriz gigante, que se redimensiona si se llena.

Chris Jester-Young
fuente
17

Es probable que todas las personas que critican a LinkedList, piensen en todos los demás tipos que han estado usando Listen Java, ArrayListy la LinkedListmayoría de las veces porque han estado antes de Java 6 y porque esos son los que se enseñan como comienzo en la mayoría de los libros.

Sin embargo, eso no quiere decir, me gustaría tener a ciegas LinkedList's o ArrayDeque' lado s. Si quieres saber, echa un vistazo al siguiente punto de referencia realizado por Brian .

La configuración de prueba considera:

  • Cada objeto de prueba es una cadena de 500 caracteres. Cada cadena es un objeto diferente en la memoria.
  • El tamaño de la matriz de prueba variará durante las pruebas.
  • Para cada combinación de tamaño de matriz / implementación de cola, se ejecutan 100 pruebas y se calcula el tiempo promedio por prueba.
  • Cada prueba consiste en llenar cada cola con todos los objetos y luego eliminarlos todos.
  • Mida el tiempo en términos de milisegundos.

Resultado de la prueba:

  • Por debajo de 10,000 elementos, las pruebas LinkedList y ArrayDeque promediaron en un nivel inferior a 1 ms.
  • A medida que los conjuntos de datos se hacen más grandes, las diferencias entre el tiempo de prueba promedio ArrayDeque y LinkedList se hacen más grandes.
  • Con un tamaño de prueba de 9,900,000 elementos, el enfoque LinkedList tomó ~ 165% más tiempo que el enfoque ArrayDeque.

Grafico:

ingrese la descripción de la imagen aquí

Para llevar:

  • Si su requerimiento es almacenar 100 o 200 elementos, no haría mucha diferencia usando cualquiera de las Colas.
  • Sin embargo, si está desarrollando en un dispositivo móvil, es posible que desee utilizar una ArrayListo ArrayDequecon una buena estimación de la capacidad máxima que puede requerir la lista debido a la estricta restricción de memoria.
  • Existe una gran cantidad de código, escrito usando una LinkedListbanda de rodadura con mucho cuidado al decidir usar un, ArrayDequeespecialmente porque NO implementa la Listinterfaz (creo que esa es una razón lo suficientemente grande). Es posible que su base de código se comunique ampliamente con la interfaz de la Lista, lo más probable es que decida saltar con un ArrayDeque. Usarlo para implementaciones internas podría ser una buena idea ...
Manish Kumar Sharma
fuente
¿Cómo capturaría este punto de referencia el tiempo de GC causado por la basura de la lista vinculada?
0xbe5077ed
3

ArrayDeque y LinkedList están implementando la interfaz Deque , pero la implementación es diferente.

Diferencias clave

  1. La clase ArrayDeque es la implementación de matriz redimensionable de la interfaz Deque y la clase LinkedList es la implementación de la lista

  2. Se pueden agregar elementos NULL a LinkedList pero no en ArrayDeque

  3. ArrayDeque es más eficiente que LinkedList para agregar y quitar operaciones en ambos extremos y la implementación de LinkedList es eficiente para eliminar el elemento actual durante la iteración

  4. La implementación de LinkedList consume más memoria que ArrayDeque

Entonces, si no tiene que admitir elementos NULL && buscando menos memoria && eficiencia de agregar / eliminar elementos en ambos extremos, ArrayDeque es el mejor

Consulte la documentación para más detalles.

Ravindra babu
fuente
13
"En la lista vinculada, se necesitará O (N) para encontrar el último elemento". No es exactamente cierto. LinkedList se implementa como una lista doblemente vinculada para que no tenga que recorrer la lista para obtener el último elemento ( header.previous.element). El reclamo de "eficiencia de memoria" también puede ser cuestionado ya que la matriz de respaldo siempre cambia de tamaño a la siguiente potencia de 2.
Clément MATHIEU
55
"Se necesitará O (N) para encontrar el último elemento" está EQUIVOCADO. La lista vinculada mantiene una referencia al último nodo y LinkedList.descendingIterator () obtiene ese nodo. Entonces obtenemos O (1) rendimiento. Ver: coffeeorientedprogramming.wordpress.com/2018/04/23/… (por lo tanto downvoting).
Leo Ufimtsev
1
Cuando utiliza un Iteratorpara acceder al último elemento, la operación es O (N) para ambas clases. Cuando usa la Dequeinterfaz común , el acceso al último elemento es O (1) para ambas clases. Independientemente del punto de vista que adopte, atribuir O (1) ArrayDequey O (N) al LinkedListmismo tiempo es incorrecto.
Holger
Todas sus sugerencias son tomadas y corregidas el contenido
Ravindra babu
1

aunque ArrayDeque<E>y LinkedList<E>ambos han implementado la Deque<E>interfaz, pero ArrayDeque usa básicamente una matriz de objetos E[]para mantener los elementos dentro de su objeto, por lo que generalmente usa el índice para ubicar los elementos de cabeza y cola.

En una palabra, simplemente funciona como Deque (con todos los métodos de Deque), pero usa la estructura de datos de la matriz. En cuanto a cuál es mejor, depende de cómo y dónde los use.

rekinyz
fuente
-1

Ese no es siempre el caso.

Por ejemplo, en el caso a continuación linkedlisttiene un mejor rendimiento que de ArrayDequeacuerdo con 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;
    }
}
rojo
fuente
-5

La complejidad de tiempo para ArrayDeque para acceder a un elemento es O (1) y para LinkList es O (N) para acceder al último elemento. ArrayDeque no es seguro para subprocesos, por lo que la sincronización manual es necesaria para que pueda acceder a través de múltiples subprocesos y que sean más rápidos.

Piyush Yawalkar
fuente
3
Si se refiere a LinkedListJava Collection, está doblemente vinculado y tiene acceso rápido a la cabeza y la cola, por lo que el acceso al último elemento también requiere O (1).
Maurice
Acceder al último elemento en LinkedList no es O (N). Si usa descendingIterator (), se realiza en O (1). Ver coffeeorientedprogramming.wordpress.com/2018/04/23/… (por lo tanto, voto negativo ).
Leo Ufimtsev
1
Ambas clases no son seguras para subprocesos. Y no hay conexión entre el comienzo de la oración "la sincronización manual es necesaria" y su final "son más rápidos".
Holger