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?
java
arrays
list
performance
euforia83
fuente
fuente
Respuestas:
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.
fuente
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.
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.
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.
fuente
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:
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:
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)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:
Integer[] array = new Integer[1];
List<Integer> list = new ArrayList<> (1);
Integer[] array = new Integer[10000];
List<Integer> list = new ArrayList<> (10000);
Resultados (en nanosegundos por llamada, 95% de confianza):
Conclusión: no hay diferencia notable .
obtener operaciones
Ejecuté 2 pruebas, ejecutando las siguientes declaraciones:
return list.get(0);
return array[0];
Resultados (en nanosegundos por llamada, 95% de confianza):
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:
list.set(0, value);
array[0] = value;
Resultados (en nanosegundos por llamada):
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.copyOf
que el rendimiento sea idéntico a la copia de la matriz (copiar una matriz a través declone
,Arrays.copyOf
oSystem.arrayCopy
no hace diferencia material en cuanto al rendimiento ).fuente
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:
fuente
Supongo que el póster original proviene de un fondo C ++ / STL que está causando cierta confusión. En C ++
std::list
hay una lista doblemente vinculada.En Java
[java.util.]List
es una interfaz libre de implementación (clase abstracta pura en términos de C ++).List
puede ser una lista doblemente vinculada:java.util.LinkedList
se proporciona. Sin embargo, 99 de cada 100 veces cuando desea hacer una nuevaList
, desea usarlajava.util.ArrayList
, que es el equivalente aproximado de C ++std::vector
. Hay otras implementaciones estándar, como las devueltas porjava.util.Collections.emptyList()
yjava.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
String
normalmente 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 inclusobyte[]
) para todos los caracteres de todas las cadenas, y luego una matriz de compensaciones. IIRC, así es como se implementa javac.fuente
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:
fuente
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;
Lista enlazada:
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.
fuente
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: stringsList
fue 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).fuente
char[]
no se toca (esto no es C).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.
fuente
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
El trie almacenaría:
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.
fuente
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:
Y aquí está la respuesta:
Basado en la matriz (la línea 16 está activa):
Basado en la lista (la línea 17 está activa):
¿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.fuente
3
veces más rápido cierto, pero insignificantemente.14ms
no es mucho tiempoComo 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:
El resultado de rendimiento está abajo:
fuente
La lista es más lenta que las matrices. Si necesita eficiencia, use matrices. Si necesita flexibilidad, use la lista.
fuente
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.
fuente
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.
fuente
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:
- 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]
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):
fuente
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.
fuente
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.
fuente
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.
fuente
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á.
fuente
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.
fuente
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):
Prueba
fuente
"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 .
fuente
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.
fuente
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.
fuente
La matriz es más rápida: toda la memoria se asigna previamente de antemano.
fuente
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ú '
fuente
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.
fuente
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?
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
(Actualización) de Java 7
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
.fuente
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).
fuente