El constructor habitual de ArrayList
es:
ArrayList<?> list = new ArrayList<>();
Pero también hay un constructor sobrecargado con un parámetro para su capacidad inicial:
ArrayList<?> list = new ArrayList<>(20);
¿Por qué es útil crear un archivo ArrayList
con una capacidad inicial cuando podemos agregarlo a nuestro gusto?
Respuestas:
Si sabe de antemano cuál
ArrayList
será el tamaño , es más eficiente especificar la capacidad inicial. Si no hace esto, la matriz interna tendrá que reasignarse repetidamente a medida que la lista crezca.Cuanto más grande sea la lista final, más tiempo ahorrará al evitar las reasignaciones.
Dicho esto, incluso sin asignación previa, se garantiza que la inserción de
n
elementos en la parte posterior de unaArrayList
tomaO(n)
tiempo total . En otras palabras, agregar un elemento es una operación amortizada de tiempo constante. Esto se logra haciendo que cada reasignación aumente exponencialmente el tamaño de la matriz, típicamente por un factor de1.5
. Con este enfoque, se puede demostrar queO(n)
el número total de operaciones es .fuente
O(n log n)
estaría haciendo horarios delog n
trabajon
. Esa es una gran sobreestimación (aunque técnicamente correcta con O grande debido a que es un límite superior). Copia s + s * 1.5 + s * 1.5 ^ 2 + ... + s * 1.5 ^ m (tal que s * 1.5 ^ m <n <s * 1.5 ^ (m + 1)) elementos en total. No soy bueno para las sumas, así que no puedo darte las matemáticas precisas de la parte superior de mi cabeza (para cambiar el tamaño del factor 2, es 2n, por lo que puede ser 1.5n dar o tomar una pequeña constante), pero no No es necesario entrecerrar los ojos para ver que esta suma es, como máximo, un factor constante mayor que n. Por lo tanto, toma O (k * n) copias, que por supuesto es O (n).Porque
ArrayList
es una estructura de datos de matriz de cambio de tamaño dinámico , lo que significa que se implementa como una matriz con un tamaño fijo inicial (predeterminado). Cuando esto se llene, la matriz se extenderá a una de doble tamaño. Esta operación es costosa, por lo que desea lo menos posible.Entonces, si sabe que su límite superior es de 20 elementos, crear la matriz con una longitud inicial de 20 es mejor que usar un valor predeterminado de, digamos, 15 y luego cambiar su tamaño
15*2 = 30
y usar solo 20 mientras desperdicia los ciclos para la expansión.PD: como dice AmitG, el factor de expansión es específico de la implementación (en este caso
(oldCapacity * 3)/2 + 1
)fuente
int newCapacity = (oldCapacity * 3)/2 + 1;
El tamaño predeterminado de Arraylist es 10 .
Entonces, si va a agregar 100 o más registros, puede ver la sobrecarga de reasignación de memoria.
Entonces, si tiene alguna idea sobre la cantidad de elementos que se almacenarán en Arraylist, es mejor crear Arraylist con ese tamaño en lugar de comenzar con 10 y luego continuar incrementándolo.
fuente
private static final int DEFAULT_CAPACITY = 10
De hecho, escribí una publicación de blog sobre el tema hace 2 meses. El artículo es para C #,
List<T>
pero JavaArrayList
tiene una implementación muy similar. Dado queArrayList
se implementa utilizando una matriz dinámica, aumenta de tamaño a pedido. Entonces, la razón del constructor de capacidad es para fines de optimización.Cuando se produce una de estas operaciones de cambio de tamaño, ArrayList copia el contenido de la matriz en una nueva matriz que tiene el doble de capacidad que la anterior. Esta operación se ejecuta en tiempo O (n) .
Ejemplo
Aquí hay un ejemplo de cómo
ArrayList
aumentaría el tamaño:Entonces, la lista comienza con una capacidad de
10
, cuando se agrega el undécimo elemento, aumenta en50% + 1
a16
. En el decimoséptimo elemento,ArrayList
se aumenta nuevamente25
y así sucesivamente. Ahora considere el ejemplo donde estamos creando una lista donde la capacidad deseada ya se conoce como1000000
. Crear elArrayList
constructor sin el tamaño llamará aArrayList.add
1000000
veces que toma O (1) normalmente u O (n) en el cambio de tamaño.Compare esto usando el constructor y luego llamando,
ArrayList.add
que se garantiza que se ejecutará en O (1) .Java vs C #
Java es como el anterior, comenzando en
10
y aumentando cada cambio de tamaño en50% + 1
. C # comienza en4
y aumenta mucho más agresivamente, duplicando en cada cambio de tamaño. El1000000
ejemplo agregado de arriba para C # utiliza3097084
operaciones.Referencias
fuente
Establecer el tamaño inicial de una ArrayList, por ejemplo, a
ArrayList<>(100)
, reduce el número de veces que debe tener lugar la reasignación de memoria interna.Ejemplo:
Como puede ver en el ejemplo anterior,
ArrayList
se puede expandir un si es necesario. Lo que esto no muestra es que el tamaño de la lista de arrastre generalmente se duplica (aunque tenga en cuenta que el nuevo tamaño depende de su implementación). Oracle cita lo siguiente :Obviamente, si no tiene idea de qué tipo de rango tendrá, establecer el tamaño probablemente no sea una buena idea; sin embargo, si tiene un rango específico en mente, establecer una capacidad inicial aumentará la eficiencia de la memoria .
fuente
ArrayList puede contener muchos valores y, al hacer inserciones iniciales grandes, puede indicarle a ArrayList que asigne un almacenamiento más grande para comenzar a no desperdiciar los ciclos de la CPU cuando intenta asignar más espacio para el siguiente elemento. Por lo tanto, asignar algo de espacio al principio es más eficiente.
fuente
Esto es para evitar posibles esfuerzos de reasignación para cada objeto individual.
new Object[]
Se crea internamente .JVM necesita esfuerzo para crear
new Object[]
cuando agrega un elemento en la lista de arrays. Si usted no tiene el código anterior (cualquier algo que creo) para su reasignación a continuación, cada vez que se invocaarraylist.add()
a continuación,new Object[]
tiene que ser creado, que no tiene sentido y estamos perdiendo el tiempo para aumentar el tamaño en 1 por cada uno de los objetos que se añadió. Por lo tanto, es mejor aumentar el tamañoObject[]
con la siguiente fórmula.(JSL ha utilizado la fórmula de predicción dada a continuación para aumentar dinámicamente la lista de arrays en lugar de crecer en 1 cada vez. Porque crecer requiere esfuerzo por parte de JVM)
fuente
add
, ya que usa alguna fórmula de crecimiento internamente. Por lo tanto, la pregunta no se responde.int newCapacity = (oldCapacity * 3)/2 + 1;
que está presente en la clase ArrayList. ¿Todavía crees que no tiene respuesta?ArrayList
la amortización la reasignación tiene lugar en cualquier caso con algún valor para la capacidad inicial. Y la pregunta es: ¿por qué usar un valor no estándar para la capacidad inicial? Además de esto: "leer entre líneas" no es algo deseado en una respuesta técnica. ;-)Creo que cada ArrayList se crea con un valor de capacidad de inicio de "10". De todos modos, si crea una ArrayList sin establecer la capacidad dentro del constructor, se creará con un valor predeterminado.
fuente
Yo diría que es una optimización. ArrayList sin capacidad inicial tendrá ~ 10 filas vacías y se expandirá cuando esté agregando.
Para tener una lista con exactamente la cantidad de elementos que necesita llamar a trimToSize ()
fuente
Según mi experiencia con
ArrayList
, dar una capacidad inicial es una buena manera de evitar los costos de reasignación. Pero tiene una advertencia. Todas las sugerencias mencionadas anteriormente dicen que uno debe proporcionar capacidad inicial solo cuando se conoce una estimación aproximada del número de elementos. Pero cuando intentamos dar una capacidad inicial sin ninguna idea, la cantidad de memoria reservada y no utilizada será un desperdicio, ya que puede que nunca sea necesaria una vez que la lista se llena con el número requerido de elementos. Lo que digo es que podemos ser pragmáticos al principio mientras asignamos capacidad, y luego encontrar una forma inteligente de conocer la capacidad mínima requerida en tiempo de ejecución. ArrayList proporciona un método llamadoensureCapacity(int minCapacity)
. Pero entonces, uno tiene que encontrar una manera inteligente ...fuente
He probado ArrayList con y sin initialCapacity y obtuve un resultado sorprendente.
Cuando configuré LOOP_NUMBER en 100,000 o menos, el resultado es que la configuración initialCapacity es eficiente.
Pero cuando configuro LOOP_NUMBER en 1,000,000 el resultado cambia a:
Finalmente, no pude entender cómo funciona.
Código de muestra:
He probado en windows8.1 y jdk1.7.0_80
fuente