Matriz o lista en Java. ¿Cual es mas rápido?

351

Tengo que guardar miles de cadenas en la memoria para poder acceder en serie en Java. ¿Debo almacenarlos en una matriz o debo usar algún tipo de Lista?

Dado que las matrices mantienen todos los datos en una porción contigua de memoria (a diferencia de las Listas), ¿causaría problemas el uso de una matriz para almacenar miles de cadenas?

euforia83
fuente
55
"Dado que las matrices mantienen todos los datos en una porción contigua de memoria", ¿tiene algún tipo de cita para respaldar esto en Java?
mate b
1
No mate Sé esto para C. Supongo que Java usaría el mismo método.
euphoria83
Dudo que los mantenga en un solo trozo de memoria.
Fortyrunner
3
Incluso si se trata de un solo bloque de memoria, solo tendría un valor aproximado de 1000 * 4 = 4kb, lo que no es mucha memoria.
CookieOfFortune
3
@mattb Eso es lo que significa "matriz" en todo CS. No es necesario citar. Las numerosas referencias en JLS y [JVM Spec] () a longitudes de matriz solo son comprensibles si las matrices son contiguas.
Marqués de Lorne

Respuestas:

358

Le sugiero que use un generador de perfiles para probar cuál es más rápido.

Mi opinión personal es que debes usar Listas.

Trabajo en una gran base de código y un grupo anterior de desarrolladores usaba matrices en todas partes . Hizo el código muy inflexible. Después de cambiar grandes porciones de ella a Listas, no notamos ninguna diferencia en la velocidad.

Fortyrunner
fuente
2
@Fortyrunner: según su experiencia, ¿existen tales opciones en Java entre la abstracción y los formularios de datos sin formato que marquen una diferencia significativa en el rendimiento?
euphoria83
44
Uno de los problemas con la medición del rendimiento es que constantemente tienes que volver a probar con nuevas versiones de Java. Estoy trabajando en un problema en el momento en que alguien usó un int en todo momento para una clave en un mapa (para ahorrar espacio / tiempo). Ahora necesitamos cambiar todas las líneas a un nuevo objeto, es doloroso.
Fortyrunner
99
Entonces ... ahora trato de mantenerme alejado de los datos en bruto. Rara vez hace una diferencia notable. Hotspot es una increíble pieza de tecnología y nunca debes intentar adivinar. Simplemente intente escribir código simple y fácil de mantener y Hotspot hará el resto.
Fortyrunner
44
Recuerde que los resultados del generador de perfiles solo son válidos para la plataforma Java en la que ejecuta el generador de perfiles. Que puede ser diferente a sus clientes.
Mikkel Løkke
44
Java eficaz recomienda listas, ya que ayudan con la interoperabilidad API y también son más seguras con la seguridad de tipos.
juanmf
164

La forma de Java es que debe considerar qué abstracción de datos se adapta mejor a sus necesidades. Recuerde que en Java una Lista es un tipo de datos abstracto, no concreto. Debe declarar las cadenas como una Lista y luego inicializarla utilizando la implementación de ArrayList.

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

Esta separación del tipo de datos abstractos y la implementación específica es uno de los aspectos clave de la programación orientada a objetos.

Una ArrayList implementa el Tipo de datos abstractos de lista utilizando una matriz como su implementación subyacente. La velocidad de acceso es prácticamente idéntica a una matriz, con las ventajas adicionales de poder sumar y restar elementos a una Lista (aunque esta es una operación O (n) con una ArrayList) y eso si decide cambiar la implementación subyacente más adelante usted puede. Por ejemplo, si se da cuenta de que necesita acceso sincronizado, puede cambiar la implementación a un Vector sin reescribir todo su código.

De hecho, ArrayList fue diseñado específicamente para reemplazar la construcción de matriz de bajo nivel en la mayoría de los contextos. Si Java se estuviera diseñando hoy, es muy posible que las matrices se hayan omitido por completo a favor de la construcción ArrayList.

Dado que las matrices mantienen todos los datos en una porción contigua de memoria (a diferencia de las Listas), ¿causaría problemas el uso de una matriz para almacenar miles de cadenas?

En Java, todas las colecciones almacenan solo referencias a objetos, no los objetos en sí. Ambas matrices y ArrayList almacenarán algunos miles de referencias en una matriz contigua, por lo que son esencialmente idénticas. Puede considerar que un bloque contiguo de unos pocos miles de referencias de 32 bits siempre estará disponible en el hardware moderno. Esto no garantiza que no se quede sin memoria, por supuesto, solo que el bloque contiguo de memoria no es difícil de cumplir.

cygil
fuente
Por supuesto, agregar puede implicar la reasignación de la matriz de respaldo, por lo que si el rendimiento es importante y el tamaño de la matriz se conoce de antemano, se debe considerar el uso de ArrayList # allowCapacity.
JesperE
66
¿No paga el costo del enlace dinámico aquí?
Uri el
2
Supongo que agregar no es O (n) en ArrayList, debería haber algún efecto de ammortización al agregar más de una vez, por ejemplo, la capacidad se duplica en lugar de aumentar solo 1.
zedoo
@zedoo Creo que significaban sumar y restar en el medio.
MalcolmOcean
"Si Java se estuviera diseñando hoy en día, es muy posible que las matrices se hayan omitido por completo a favor de la construcción ArrayList". ... Dudo seriamente que esto sea cierto. Si fuera la JVM que se reescribe hoy, entonces lo que usted ha dicho es ciertamente posible. Pero con la JVM que tenemos, las matrices son un tipo fundamental en Java.
scottb
100

Aunque las respuestas que proponen usar ArrayList tienen sentido en la mayoría de los escenarios, la pregunta real sobre el rendimiento relativo no ha sido respondida realmente.

Hay algunas cosas que puede hacer con una matriz:

  • crearlo
  • establecer un elemento
  • obtener un artículo
  • clonar / copiar

Conclusión general

Aunque las operaciones get y set son algo más lentas en una ArrayList (resp. 1 y 3 nanosegundos por llamada en mi máquina), hay muy poca sobrecarga de usar una ArrayList frente a una matriz para cualquier uso no intensivo. Sin embargo, hay algunas cosas a tener en cuenta:

  • cambiar el tamaño de las operaciones en una lista (cuando se llama list.add(...)) es costoso y uno debe intentar establecer la capacidad inicial en un nivel adecuado cuando sea posible (tenga en cuenta que surge el mismo problema cuando se usa una matriz)
  • Cuando se trata de primitivas, las matrices pueden ser significativamente más rápidas ya que permitirán evitar muchas conversiones de boxeo / unboxing
  • una aplicación que solo obtiene / establece valores en una ArrayList (¡no es muy común!) podría ver una ganancia de rendimiento de más del 25% al ​​cambiar a una matriz

Resultados detallados

Aquí están los resultados que midí para esas tres operaciones usando la biblioteca de evaluación comparativa jmh (veces en nanosegundos) con JDK 7 en una máquina de escritorio estándar x86. Tenga en cuenta que ArrayList nunca cambia de tamaño en las pruebas para asegurarse de que los resultados sean comparables. Código de referencia disponible aquí .

Creación de Array / ArrayList

Ejecuté 4 pruebas, ejecutando las siguientes declaraciones:

  • createArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • createArray10000: Integer[] array = new Integer[10000];
  • createList10000: List<Integer> list = new ArrayList<> (10000);

Resultados (en nanosegundos por llamada, 95% de confianza):

a.p.g.a.ArrayVsList.CreateArray1         [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1          [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000    [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000     [396.706, 401.266]

Conclusión: no hay diferencia notable .

obtener operaciones

Ejecuté 2 pruebas, ejecutando las siguientes declaraciones:

  • getList: return list.get(0);
  • getArray: return array[0];

Resultados (en nanosegundos por llamada, 95% de confianza):

a.p.g.a.ArrayVsList.getArray   [2.958, 2.984]
a.p.g.a.ArrayVsList.getList    [3.841, 3.874]

Conclusión: obtener de una matriz es aproximadamente un 25% más rápido que obtener de una ArrayList, aunque la diferencia es solo del orden de un nanosegundo.

establecer operaciones

Ejecuté 2 pruebas, ejecutando las siguientes declaraciones:

  • setList: list.set(0, value);
  • setArray: array[0] = value;

Resultados (en nanosegundos por llamada):

a.p.g.a.ArrayVsList.setArray   [4.201, 4.236]
a.p.g.a.ArrayVsList.setList    [6.783, 6.877]

Conclusión: las operaciones de configuración en las matrices son aproximadamente un 40% más rápidas que en las listas, pero, en cuanto a get, cada operación de configuración requiere unos pocos nanosegundos, por lo que para que la diferencia llegue a 1 segundo, sería necesario establecer elementos en la lista / matriz cientos de millones de veces!

clonar / copiar

El constructor de copias de ArrayList delega para Arrays.copyOfque el rendimiento sea idéntico a la copia de la matriz (copiar una matriz a través de clone, Arrays.copyOfo System.arrayCopy no hace diferencia material en cuanto al rendimiento ).

asilias
fuente
1
Buen análisis Sin embargo, con respecto a su comentario "cuando se trata de primitivas, las matrices pueden ser significativamente más rápidas, ya que permitirán evitar muchas conversiones de boxeo / unboxing", puede tener su pastel y comerlo también, con una Lista respaldada por matriz primitiva implementación; por ejemplo: github.com/scijava/scijava-common/blob/master/src/main/java/org/… . De hecho, estoy bastante sorprendido de que tal cosa no haya llegado al núcleo de Java.
ctrueden
2
@ctrueden sí, el comentario se aplicó a la JDK ArrayList estándar. trove4j es una biblioteca bien conocida que admite listas primitivas. Java 8 trae algunas mejoras con varios flujos primitivos especializados.
Assylias
No sé cómo funcionan los puntos de referencia jmh, pero ¿tienen en cuenta la compilación JIT que puede suceder? El rendimiento de una aplicación Java puede variar con el tiempo a medida que la JVM compila su código.
Hoffmann
@Hoffmann Sí, incluye una fase de calentamiento que se excluye de la medición.
Assylias
97

Debería preferir los tipos genéricos sobre las matrices. Como han mencionado otros, las matrices son inflexibles y no tienen el poder expresivo de los tipos genéricos. (Sin embargo, admiten la verificación de tipos en tiempo de ejecución, pero eso se mezcla mal con los tipos genéricos).

Pero, como siempre, al optimizar siempre debe seguir estos pasos:

  • No optimice hasta que tenga una versión agradable, limpia y funcional de su código. El cambio a tipos genéricos ya podría estar motivado en este paso.
  • Cuando tenga una versión agradable y limpia, decida si es lo suficientemente rápida.
  • Si no es lo suficientemente rápido, mida su rendimiento . Este paso es importante por dos razones. Si no mide, no (1) sabrá el impacto de las optimizaciones que realice y (2) sepa dónde optimizar.
  • Optimiza la parte más popular de tu código.
  • Mide de nuevo. Esto es tan importante como medir antes. Si la optimización no mejoró las cosas, reviértala . Recuerde, el código sin la optimización era limpio, agradable y funcionaba.
JesperE
fuente
24

Supongo que el póster original proviene de un fondo C ++ / STL que está causando cierta confusión. En C ++ std::listhay una lista doblemente vinculada.

En Java [java.util.]Listes una interfaz libre de implementación (clase abstracta pura en términos de C ++). Listpuede ser una lista doblemente vinculada: java.util.LinkedListse proporciona. Sin embargo, 99 de cada 100 veces cuando desea hacer una nueva List, desea usarla java.util.ArrayList, que es el equivalente aproximado de C ++ std::vector. Hay otras implementaciones estándar, como las devueltas por java.util.Collections.emptyList()y java.util.Arrays.asList().

Desde el punto de vista del rendimiento, es muy difícil tener que pasar por una interfaz y un objeto adicional, sin embargo, la alineación en tiempo de ejecución significa que esto rara vez tiene alguna importancia. También recuerde que Stringnormalmente son un objeto más una matriz. Entonces, para cada entrada, probablemente tenga otros dos objetos. En C ++ std::vector<std::string>, aunque se copia por valor sin un puntero como tal, las matrices de caracteres formarán un objeto para la cadena (y generalmente no se compartirán).

Si este código en particular es realmente sensible al rendimiento, puede crear una sola char[]matriz (o incluso byte[]) para todos los caracteres de todas las cadenas, y luego una matriz de compensaciones. IIRC, así es como se implementa javac.

Tom Hawtin - tackline
fuente
1
Gracias por la respuesta. Pero no, no estoy confundiendo la lista de C ++ con la lista de interfaces de Java. Hice la pregunta de esa manera porque quería comparar el rendimiento de las implementaciones de List como ArrayList y Vector con matrices sin procesar.
euphoria83
Tanto ArrayList como Vector "mantienen todos los datos en una porción contigua de memoria".
Tom Hawtin - tackline
13

Estoy de acuerdo en que, en la mayoría de los casos, debe elegir la flexibilidad y la elegancia de ArrayLists en lugar de las matrices, y en la mayoría de los casos el impacto en el rendimiento del programa será insignificante.

Sin embargo, si está haciendo iteraciones constantes y pesadas con poco cambio estructural (sin adiciones ni eliminaciones) para, por ejemplo, la representación de gráficos de software o una máquina virtual personalizada, mis pruebas de evaluación comparativa de acceso secuencial muestran que las ArrayLists son 1.5 veces más lentas que las matrices en mi sistema (Java 1.6 en mi iMac de un año).

Algún código:

import java.util.*;

public class ArrayVsArrayList {
    static public void main( String[] args ) {

        String[] array = new String[300];
        ArrayList<String> list = new ArrayList<String>(300);

        for (int i=0; i<300; ++i) {
            if (Math.random() > 0.5) {
                array[i] = "abc";
            } else {
                array[i] = "xyz";
            }

            list.add( array[i] );
        }

        int iterations = 100000000;
        long start_ms;
        int sum;

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += array[j].length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
        // Prints ~13,500 ms on my system

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += list.get(j).length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
        // Prints ~20,800 ms on my system - about 1.5x slower than direct array access
    }
}
AbePralle
fuente
Me pareció una respuesta interesante, pero me pregunto si es aún peor si ArrayList no se inicializa con un tamaño inicial en la memoria. En general, el beneficio de usar ArrayList sobre una matriz nativa en cierto sentido es que no lo sabrá y no tendrá que preocuparse. Las ArrayLists se crean por defecto con una longitud inicial de 10 y luego se redimensionan. Creo que el cambio de tamaño es costoso. No he intentado compararlo obviamente.
Zak Patterson
44
Este micro de referencia tiene defectos (sin calentar, las operaciones no en un método separado lo que la parte ArrayList no está optimizado por el JIT etc.)
assylias
Estoy de acuerdo con las asilias. No se debe confiar en los resultados de este punto de referencia.
Stephen C
@StephenC He agregado un micro punto de referencia adecuado (que muestra que las operaciones de obtención son comparables).
Assylias
11

Bueno, primero vale la pena aclarar ¿te refieres a "lista" en el sentido clásico de estructuras de datos comp sci (es decir, una lista vinculada) o te refieres a java.util.List? Si te refieres a java.util.List, es una interfaz. Si desea usar una matriz, simplemente use la implementación ArrayList y obtendrá un comportamiento y una semántica similares a una matriz. Problema resuelto.

Si te refieres a una matriz frente a una lista vinculada, es un argumento ligeramente diferente por el cual volvemos a Big O (aquí hay una explicación sencilla en inglés si este es un término desconocido).

Formación;

  • Acceso aleatorio: O (1);
  • Insertar: O (n);
  • Eliminar: O (n).

Lista enlazada:

  • Acceso aleatorio: O (n);
  • Insertar: O (1);
  • Eliminar: O (1).

Así que eliges el que mejor se adapte a cómo redimensionas tu matriz. Si cambia el tamaño, inserte y elimine mucho, quizás una lista vinculada sea una mejor opción. Lo mismo ocurre si el acceso aleatorio es raro. Menciona el acceso en serie. Si principalmente está haciendo acceso en serie con muy poca modificación, entonces probablemente no importa cuál elija.

Las listas vinculadas tienen una sobrecarga un poco más alta ya que, como usted dice, está lidiando con bloques de memoria potencialmente no contiguos y punteros (efectivamente) al siguiente elemento. Sin embargo, eso probablemente no sea un factor importante a menos que esté lidiando con millones de entradas.

cletus
fuente
i media interfaz java.util.List
euphoria83
1
El acceso aleatorio O (n) en la lista vinculada me parece un gran problema.
Bjorn
11

Escribí un pequeño punto de referencia para comparar ArrayLists con Arrays. En mi computadora portátil antigua, el tiempo para atravesar una lista de arrays de 5000 elementos, 1000 veces, fue aproximadamente 10 milisegundos más lento que el código de matriz equivalente.

Entonces, si no está haciendo nada más que iterar la lista, y lo está haciendo mucho, entonces tal vez valga la pena la optimización. De lo contrario, haría uso de la lista, ya que hará más fácil cuando se hace necesario optimizar el código.

NB I hice notar que el uso for String s: stringsListfue de un 50% más lento que el uso de un viejo estilo de bucle para acceder a la lista. Vaya figura ... Aquí están las dos funciones que cronometré; la matriz y la lista se llenaron con 5000 cadenas aleatorias (diferentes).

private static void readArray(String[] strings) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < strings.length; i++) {
            totalchars += strings[i].length();

        }
    }
}

private static void readArrayList(List<String> stringsList) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < stringsList.size(); i++) {
            totalchars += stringsList.get(i).length();
        }
    }
}
Chris May
fuente
@ Chris May: ¡Buen trabajo! ¿Cuáles son los tiempos de ejecución reales para ambos? ¿Me puede decir el tamaño de las cuerdas que estaba usando? Además, como el uso de 'String s: stringsList' hizo que tomara más tiempo, este es mi principal temor al usar las abstracciones más altas en Java en general.
euphoria83
Realmente no importa cuánto duran las cadenas para esta marca de mcirob. No hay gc y char[]no se toca (esto no es C).
Tom Hawtin - tackline
Los tiempos típicos para mí fueron ~ 25 ms para la versión de matriz, ~ 35 ms para la versión ArrayList. Las cuerdas tenían 15-20 caracteres de largo. Como dice Tom, el tamaño de la cadena no hace mucha diferencia, con una cadena de ~ 100 caracteres los tiempos fueron casi iguales.
Chris May
3
¿Cómo lo midiste? La medición ingenua en micro puntos de referencia de Java generalmente genera más información errónea que información. Cuidado con la declaración anterior.
jmg
6

No, porque técnicamente, la matriz solo almacena la referencia a las cadenas. Las cadenas mismas se asignan en una ubicación diferente. Para mil artículos, diría que una lista sería mejor, es más lenta, pero ofrece más flexibilidad y es más fácil de usar, especialmente si va a cambiar su tamaño.

CookieOfFortune
fuente
55
La lista también almacena solo referencias a cadenas.
Peter Štibraný
6

Si tiene miles, considere usar un trie. Un trie es una estructura en forma de árbol que combina los prefijos comunes de la cadena almacenada.

Por ejemplo, si las cadenas fueran

intern
international
internationalize
internet
internets

El trie almacenaría:

intern
 -> \0
 international
 -> \0
 -> ize\0
 net
 ->\0
 ->s\0

Las cadenas requieren 57 caracteres (incluido el terminador nulo, '\ 0') para el almacenamiento, más el tamaño del objeto de cadena que los contiene. (En verdad, probablemente deberíamos redondear todos los tamaños hasta múltiplos de 16, pero ...) Llámalo 57 + 5 = 62 bytes, aproximadamente.

El trie requiere 29 (incluido el terminador nulo, '\ 0') para el almacenamiento, más el tamaño de los nodos trie, que son una referencia a una matriz y una lista de nodos trie secundarios.

Para este ejemplo, eso probablemente sea casi lo mismo; para miles, probablemente salga menos siempre que tenga prefijos comunes.

Ahora, cuando use el trie en otro código, tendrá que convertir a String, probablemente usando un StringBuffer como intermediario. Si muchas de las cadenas están en uso a la vez como cadenas, fuera del trie, es una pérdida.

Pero si solo usa unos pocos en ese momento, por ejemplo, para buscar cosas en un diccionario, el trie puede ahorrarle mucho espacio. Definitivamente menos espacio que almacenarlos en un HashSet.

Dices que estás accediendo a ellos "en serie"; si eso significa secuencialmente alfabéticamente, el trie obviamente también te da un orden alfabético de forma gratuita, si lo iteras primero en profundidad.

tpdi
fuente
1
¿Es trie como una biblioteca o cómo la creo?
euphoria83
Un trie sería útil solo en el caso de cadenas simbólicas, no si alguien almacena el texto en ejecución como cadenas.
MN
5

ACTUALIZAR:

Como señaló Mark, no hay una diferencia significativa después del calentamiento de JVM (varios pases de prueba). Se verifica con una matriz recreada o incluso un nuevo pase que comienza con una nueva fila de matriz. Con gran probabilidad, esta matriz simple de signos con acceso al índice no se debe utilizar en favor de las colecciones.

Aún así, los primeros 1-2 pases de matriz simple son 2-3 veces más rápidos.

POSTE ORIGINAL:

Demasiadas palabras para el tema demasiado simple para verificar. Sin ninguna pregunta, la matriz es varias veces más rápida que cualquier contenedor de clase . Corro sobre esta pregunta buscando alternativas para mi sección crítica de rendimiento. Aquí está el código prototipo que construí para verificar la situación real:

import java.util.List;
import java.util.Arrays;

public class IterationTest {

    private static final long MAX_ITERATIONS = 1000000000;

    public static void main(String [] args) {

        Integer [] array = {1, 5, 3, 5};
        List<Integer> list = Arrays.asList(array);

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i) {
//            for (int e : array) {
            for (int e : list) {
                test_sum += e;
            }
        }
        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
}

Y aquí está la respuesta:

Basado en la matriz (la línea 16 está activa):

Time: 7064

Basado en la lista (la línea 17 está activa):

Time: 20950

¿Algún comentario más sobre 'más rápido'? Esto se entiende bastante. La pregunta es cuándo es aproximadamente 3 veces más rápido que la flexibilidad de List. Pero esta es otra pregunta. Por cierto, también verifiqué esto basado en construcción manual ArrayList. Casi el mismo resultado.

Roman Nikitchenko
fuente
2
3veces más rápido cierto, pero insignificantemente. 14msno es mucho tiempo
0x6C38
1
Benchmark no está considerando el calentamiento de JVM. Cambie main () a test () y llame a test desde main repetidamente. Para la tercera o cuarta prueba, se ejecuta muchas veces más rápido. En ese momento, veo que la matriz es aproximadamente 9 veces más rápida que la matriz.
Mike
5

Como ya hay muchas buenas respuestas aquí, me gustaría brindarle alguna otra información de visión práctica, que es la comparación de rendimiento de inserción e iteración: matriz primitiva vs lista vinculada en Java.

Esta es una simple verificación de rendimiento real.
Por lo tanto, el resultado dependerá del rendimiento de la máquina.

El código fuente utilizado para esto está a continuación:

import java.util.Iterator;
import java.util.LinkedList;

public class Array_vs_LinkedList {

    private final static int MAX_SIZE = 40000000;

    public static void main(String[] args) {

        LinkedList lList = new LinkedList(); 

        /* insertion performance check */

        long startTime = System.currentTimeMillis();

        for (int i=0; i<MAX_SIZE; i++) {
            lList.add(i);
        }

        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");

        int[] arr = new int[MAX_SIZE];

        startTime = System.currentTimeMillis();
        for(int i=0; i<MAX_SIZE; i++){
            arr[i] = i; 
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        /* iteration performance check */

        startTime = System.currentTimeMillis();

        Iterator itr = lList.iterator();

        while(itr.hasNext()) {
            itr.next();
            // System.out.println("Linked list running : " + itr.next());
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        startTime = System.currentTimeMillis();

        int t = 0;
        for (int i=0; i < MAX_SIZE; i++) {
            t = arr[i];
            // System.out.println("array running : " + i);
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
    }
}

El resultado de rendimiento está abajo:

ingrese la descripción de la imagen aquí

boraseoksoon
fuente
4

La lista es más lenta que las matrices. Si necesita eficiencia, use matrices. Si necesita flexibilidad, use la lista.

Guerrero
fuente
4

Recuerde que una ArrayList encapsula una matriz, por lo que hay poca diferencia en comparación con el uso de una matriz primitiva (excepto por el hecho de que una Lista es mucho más fácil de trabajar en Java).

El único momento en el que tiene sentido preferir una matriz a una ArrayList es cuando almacena primitivas, es decir, byte, int, etc. y necesita la eficiencia espacial particular que obtiene al usar matrices primitivas.

Nuoji
fuente
4

La elección de matriz vs. lista no es tan importante (considerando el rendimiento) en el caso de almacenar objetos de cadena. Debido a que tanto la matriz como la lista almacenarán referencias de objetos de cadena, no los objetos reales.

  1. Si el número de cadenas es casi constante, use una matriz (o ArrayList). Pero si el número varía demasiado, será mejor que uses LinkedList.
  2. Si hay (o habrá) necesidad de agregar o eliminar elementos en el medio, entonces ciertamente debe usar LinkedList.
Emre
fuente
4

Vine aquí para tener una mejor idea del impacto en el rendimiento del uso de listas sobre matrices. Tuve que adaptar el código aquí para mi escenario: matriz / lista de ~ 1000 ints usando principalmente getters, lo que significa matriz [j] vs. list.get (j)

Tomando lo mejor de 7 para no ser científico al respecto (los primeros con una lista donde 2.5 veces más lento) obtengo esto:

array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator

array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)

- Entonces, aproximadamente un 30% más rápido con array

La segunda razón para publicar ahora es que nadie menciona el impacto si hace código matemático / matricial / de simulación / optimización con bucles anidados .

Supongamos que tiene tres niveles anidados y que el bucle interno es el doble de lento que está viendo un impacto de rendimiento 8 veces mayor. Algo que funcionaría en un día ahora lleva una semana.

* EDITAR Muy sorprendido aquí, por patadas intenté declarar int [1000] en lugar de Integer [1000]

array int[] best 299ms iterator
array int[] best 296ms getter

El uso de Integer [] vs. int [] representa un doble golpe de rendimiento, ListArray con iterador es 3 veces más lento que int []. Realmente pensé que las implementaciones de la lista de Java eran similares a las matrices nativas ...

Código de referencia (llame varias veces):

    public static void testArray()
    {
        final long MAX_ITERATIONS = 1000000;
        final int MAX_LENGTH = 1000;

        Random r = new Random();

        //Integer[] array = new Integer[MAX_LENGTH];
        int[] array = new int[MAX_LENGTH];

        List<Integer> list = new ArrayList<Integer>()
        {{
            for (int i = 0; i < MAX_LENGTH; ++i)
            {
                int val = r.nextInt();
                add(val);
                array[i] = val;
            }
        }};

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i)
        {
//          for (int e : array)
//          for (int e : list)          
            for (int j = 0; j < MAX_LENGTH; ++j)
            {
                int e = array[j];
//              int e = list.get(j);
                test_sum += e;
            }
        }

        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
Xult
fuente
3

Si sabe de antemano qué tan grandes son los datos, una matriz será más rápida.

Una lista es más flexible. Puede usar una ArrayList respaldada por una matriz.

TofuBeer
fuente
ArrayList tiene un método sureCapacity () que asigna previamente la matriz de respaldo al tamaño especificado.
JesperE
O puede especificar el tamaño en el momento de la construcción. También "más rápido" aquí significa "unos pocos microsegundos para asignar dos áreas de memoria en lugar de una"
Aaron Digulla
3

Si puede vivir con un tamaño fijo, las matrices serán más rápidas y necesitarán menos memoria.

Si necesita la flexibilidad de la interfaz Lista para agregar y quitar elementos, la pregunta sigue siendo qué implementación debe elegir. A menudo, ArrayList se recomienda y se usa para cualquier caso, pero también ArrayList tiene sus problemas de rendimiento si los elementos al principio o en el medio de la lista deben eliminarse o insertarse.

Por lo tanto, puede echar un vistazo a http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list que presenta GapList. Esta nueva implementación de la lista combina las fortalezas de ArrayList y LinkedList, lo que resulta en un muy buen rendimiento para casi todas las operaciones.

Thomas Mauch
fuente
2

Dependiendo de la implementación. Es posible que un conjunto de tipos primitivos sea más pequeño y más eficiente que ArrayList. Esto se debe a que la matriz almacenará los valores directamente en un bloque contiguo de memoria, mientras que la implementación más simple de ArrayList almacenará punteros a cada valor. Especialmente en una plataforma de 64 bits, esto puede hacer una gran diferencia.

Por supuesto, es posible que la implementación de jvm tenga un caso especial para esta situación, en cuyo caso el rendimiento será el mismo.

JRalph
fuente
2

La lista es la forma preferida en Java 1.5 y posteriores, ya que puede usar genéricos. Las matrices no pueden tener genéricos. También las matrices tienen una longitud predefinida, que no puede crecer dinámicamente. Inicializar una matriz con un tamaño grande no es una buena idea. ArrayList es la forma de declarar una matriz con genéricos y puede crecer dinámicamente. Pero si eliminar e insertar se usa con más frecuencia, la lista vinculada es la estructura de datos más rápida que se utilizará.

Shehan Simen
fuente
2

Las matrices se recomiendan en todas partes donde puede usarlas en lugar de la lista, especialmente en caso de que sepa que el recuento y el tamaño de los artículos no cambiarían.

Consulte las mejores prácticas de Oracle Java: http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

Por supuesto, si necesita agregar y eliminar objetos de la colección muchas veces, listas de uso fáciles.

Nik
fuente
La documentación a la que se vinculó tiene más de 10 años, es decir, se aplica a Java 1.3. Se han realizado importantes mejoras de rendimiento desde entonces ...
assylias
@assylias vea las respuestas anteriores, contienen pruebas de rendimiento que dicen que los arreglos son más rápidos
Nik
3
Sé que escribí uno de ellos. Pero no creo que "las matrices se recomiendan en todas partes donde se pueden usar en lugar de listas " es un buen consejo. ArrayList debería ser la opción predeterminada en la mayoría de las situaciones a menos que se trate de primitivas y su código sea sensible al rendimiento.
Assylias
2

Ninguna de las respuestas tenía información que me interesara: exploración repetitiva de la misma matriz muchas veces. Tuve que crear una prueba JMH para esto.

Resultados (Java 1.8.0_66 x32, la iteración de matriz simple es al menos 5 veces más rápida que ArrayList):

Benchmark                    Mode  Cnt   Score   Error  Units
MyBenchmark.testArrayForGet  avgt   10   8.121 ? 0.233  ms/op
MyBenchmark.testListForGet   avgt   10  37.416 ? 0.094  ms/op
MyBenchmark.testListForEach  avgt   10  75.674 ? 1.897  ms/op

Prueba

package my.jmh.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {

    public final static int ARR_SIZE = 100;
    public final static int ITER_COUNT = 100000;

    String arr[] = new String[ARR_SIZE];
    List<String> list = new ArrayList<>(ARR_SIZE);

    public MyBenchmark() {
        for( int i = 0; i < ARR_SIZE; i++ ) {
            list.add(null);
        }
    }

    @Benchmark
    public void testListForEach() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( String str : list ) {
                if( str != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testListForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( list.get(j) != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testArrayForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( arr[j] != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

}
Xtra Coder
fuente
2

"Miles" no es un gran número. Unos pocos miles de cadenas de longitud de párrafo son del orden de un par de megabytes de tamaño. Si todo lo que quiere hacer es acceder a estos en serie, use una Lista inmutable de enlace único .

Apocalipsis
fuente
8 bytes en la mayoría de las implementaciones de 64 bits.
Tom Hawtin - tackline
¿Hay alguna evidencia de que esto sea más rápido que java.util.LinkedList? ¿Cuál es también 'en memoria'? También se puede hacer inmutable, como si eso hiciera alguna diferencia.
Marqués de Lorne
1

No caiga en la trampa de la optimización sin una evaluación comparativa adecuada. Como otros han sugerido, use un perfilador antes de hacer cualquier suposición.

Las diferentes estructuras de datos que ha enumerado tienen diferentes propósitos. Una lista es muy eficiente para insertar elementos al principio y al final, pero sufre mucho al acceder a elementos aleatorios. Una matriz tiene almacenamiento fijo pero proporciona acceso aleatorio rápido. Finalmente, una ArrayList mejora la interfaz de una matriz al permitirle crecer. Normalmente, la estructura de datos que se utilizará debe estar dictada por cómo se accederá o agregará la información almacenada.

Sobre el consumo de memoria. Parece que estás mezclando algunas cosas. Una matriz solo le dará una porción continua de memoria para el tipo de datos que tiene. No olvide que Java tiene tipos de datos fijos: boolean, char, int, long, float y Object (esto incluye todos los objetos, incluso una matriz es un Object). Significa que si declara una matriz de cadenas de cadenas [1000] o MyObject myObjects [1000], solo obtendrá 1000 cajas de memoria lo suficientemente grandes como para almacenar la ubicación (referencias o punteros) de los objetos. No obtienes 1000 cajas de memoria lo suficientemente grandes como para ajustarse al tamaño de los objetos. No olvide que sus objetos se crean primero con "nuevo". Esto es cuando se realiza la asignación de memoria y luego se almacena una referencia (su dirección de memoria) en la matriz. El objeto no se copia en la matriz solo es su referencia.

potilo
fuente
1

No creo que haga una diferencia real para Strings. Lo que es contiguo en una matriz de cadenas son las referencias a las cadenas, las cadenas se almacenan en lugares aleatorios en la memoria.

Las matrices frente a las listas pueden marcar la diferencia para los tipos primitivos, no para los objetos. Si conoce de antemano el número de elementos y no necesita flexibilidad, una matriz de millones de enteros o dobles será más eficiente en memoria y marginalmente en velocidad que una lista, porque de hecho se almacenarán de forma contigua y se accederá al instante. Es por eso que Java todavía usa matrices de caracteres para cadenas, matrices de entradas para datos de imagen, etc.

PhiLho
fuente
1

La matriz es más rápida: toda la memoria se asigna previamente de antemano.

Yakov Fain
fuente
1

Muchas microbenchmarks dadas aquí han encontrado números de unos pocos nanosegundos para cosas como lecturas de array / ArrayList. Esto es bastante razonable si todo está en su caché L1.

Un caché de nivel superior o acceso a la memoria principal puede tener tiempos de orden de magnitud de algo como 10nS-100nS, frente a más como 1nS para caché L1. Acceder a un ArrayList tiene una indirección de memoria adicional, y en una aplicación real puede pagar este costo desde casi nunca hasta cada vez, dependiendo de lo que esté haciendo su código entre los accesos. Y, por supuesto, si tiene muchas ArrayLists pequeñas, esto podría aumentar su uso de memoria y hacer que sea más probable que tenga errores de caché.

El póster original parece estar usando solo uno y acceder a muchos contenidos en poco tiempo, por lo que no debería ser una gran dificultad. Pero puede ser diferente para otras personas, y debe tener cuidado al interpretar microbenchmarks.

Sin embargo, las cadenas de Java son terriblemente derrochadoras, especialmente si almacena muchos pequeños (solo mírelos con un analizador de memoria, parece ser> 60 bytes para una cadena de unos pocos caracteres). Una matriz de cadenas tiene una dirección indirecta al objeto String, y otra desde el objeto String a un char [] que contiene la cadena misma. Si algo va a volar tu caché L1 es esto, combinado con miles o decenas de miles de cadenas. Entonces, si usted es serio, realmente serio, acerca de obtener el mayor rendimiento posible, entonces podría considerar hacerlo de manera diferente. Podría, por ejemplo, mantener dos matrices, una char [] con todas las cadenas, una tras otra, y una int [] con desplazamientos al inicio. Será un PITA para hacer cualquier cosa, y casi seguro que no lo necesita. Y si lo haces, tú '

Alex Hayward
fuente
0

Depende de cómo tenga que acceder.

Después de almacenar, si principalmente desea realizar una operación de búsqueda, con poca o ninguna inserción / eliminación, vaya a Array (ya que la búsqueda se realiza en O (1) en matrices, mientras que agregar / eliminar puede necesitar reordenar los elementos) .

Después de almacenar, si su propósito principal es agregar / eliminar cadenas, con poca o ninguna operación de búsqueda, vaya a Lista.

Vikram
fuente
0

ArrayList usa internamente un objeto de matriz para agregar (o almacenar) los elementos. En otras palabras, ArrayList está respaldado por la estructura de datos Array. La matriz de ArrayList es redimensionable (o dinámica).

La matriz es más rápida que la matriz porque ArrayList usa la matriz internamente. si podemos agregar elementos directamente en Array e indirectamente agregar elementos en Array a través de ArrayList, siempre el mecanismo directo es más rápido que el mecanismo indirecto.

Hay dos métodos add () sobrecargados en la clase ArrayList:
1 add(Object) .: agrega el objeto al final de la lista.
2 add(int index , Object ) .: inserta el objeto especificado en la posición especificada en la lista.

¿Cómo crece dinámicamente el tamaño de ArrayList?

public boolean add(E e)        
{       
     ensureCapacity(size+1);
     elementData[size++] = e;         
     return true;
}

Un punto importante a tener en cuenta del código anterior es que estamos verificando la capacidad de ArrayList, antes de agregar el elemento. sureCapacity () determina cuál es el tamaño actual de los elementos ocupados y cuál es el tamaño máximo de la matriz. Si el tamaño de los elementos rellenos (incluido el nuevo elemento que se agregará a la clase ArrayList) es mayor que el tamaño máximo de la matriz, aumente el tamaño de la matriz. Pero el tamaño de la matriz no se puede aumentar dinámicamente. Entonces, lo que sucede internamente es que se crea una nueva matriz con capacidad

Hasta Java 6

int newCapacity = (oldCapacity * 3)/2 + 1;

(Actualización) de Java 7

 int newCapacity = oldCapacity + (oldCapacity >> 1);

Además, los datos de la matriz anterior se copian en la matriz nueva.

Tener métodos generales en ArrayList es por eso que Array es más rápido que ArrayList.

Vipin Jain
fuente
0

Matrices: siempre sería mejor cuando tenemos que lograr una recuperación de resultados más rápida

Listas: realiza resultados en la inserción y eliminación, ya que se pueden hacer en O (1) y esto también proporciona métodos para agregar, recuperar y eliminar datos fácilmente. Mucho más fácil de usar.

Pero recuerde siempre que la obtención de datos sería rápida cuando se conoce la posición del índice en la matriz donde se almacenan los datos.

Esto podría lograrse bien clasificando la matriz. Por lo tanto, esto aumenta el tiempo para recuperar los datos (es decir, almacenar los datos + ordenar los datos + buscar la posición donde se encuentran los datos). Por lo tanto, esto aumenta la latencia adicional para recuperar los datos de la matriz, incluso si pueden ser buenos para recuperar los datos antes.

Por lo tanto, esto podría resolverse con una estructura de datos trie o una estructura de datos ternarios. Como se discutió anteriormente, la estructura de datos trie sería muy eficiente en la búsqueda de los datos, la búsqueda de una palabra en particular se puede hacer en magnitud O (1). Cuando el tiempo importa, es decir; Si tiene que buscar y recuperar datos rápidamente, puede utilizar la estructura de datos trie.

Si desea que su espacio de memoria se consuma menos y desea tener un mejor rendimiento, vaya con la estructura de datos ternarios. Ambos son adecuados para almacenar una gran cantidad de cadenas (por ejemplo, como palabras contenidas en el diccionario).

revs Rajasuba Subramanian
fuente