llenar - En Java 8, ¿por qué la capacidad predeterminada de ArrayList ahora es cero?
listas en java ejemplos (6)
Como recuerdo, antes de Java 8, la capacidad predeterminada de
ArrayList
era 10.
Sorprendentemente, el comentario sobre el constructor (vacío) predeterminado aún dice:
Constructs an empty list with an initial capacity of ten.
De
ArrayList.java
:
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
...
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
El tamaño predeterminado de ArrayList en JAVA 8 es todavía 10. El único cambio realizado en JAVA 8 es que si un codificador agrega elementos inferiores a 10, los lugares en blanco restantes de la lista de matrices no se especifican como nulos. Dicho esto porque yo mismo he pasado por esta situación y el eclipse me hizo investigar este cambio de JAVA 8.
Puede justificar este cambio mirando la siguiente captura de pantalla. En él puede ver que el tamaño de ArrayList se especifica como 10 en Object [10] pero el número de elementos que se muestran son solo 7. Los elementos de valor nulo restantes no se muestran aquí. En JAVA 7, la captura de pantalla siguiente es la misma con un solo cambio, que es que los elementos de valor nulo también se muestran para los cuales el codificador necesita escribir código para manejar valores nulos si está iterando la lista de matriz completa, mientras que en JAVA 8 esta carga se elimina El jefe del codificador / desarrollador.
En java 8, la capacidad predeterminada de ArrayList es 0 hasta que agreguemos al menos un objeto al objeto ArrayList (puede llamarlo inicialización diferida).
Ahora la pregunta es ¿por qué este cambio se ha realizado en JAVA 8?
La respuesta es ahorrar consumo de memoria. Se crean millones de objetos de lista de matriz en aplicaciones java en tiempo real. El tamaño predeterminado de 10 objetos significa que asignamos 10 punteros (40 u 80 bytes) para la matriz subyacente en la creación y los rellenamos con valores nulos. Una matriz vacía (llena de nulos) ocupa mucha memoria.
La inicialización diferida pospone este consumo de memoria hasta el momento en que realmente usará la lista de matrices.
Consulte el siguiente código para obtener ayuda.
ArrayList al = new ArrayList(); //Size: 0, Capacity: 0
ArrayList al = new ArrayList(5); //Size: 0, Capacity: 5
ArrayList al = new ArrayList(new ArrayList(5)); //Size: 0, Capacity: 0
al.add( "shailesh" ); //Size: 1, Capacity: 10
public static void main( String[] args )
throws Exception
{
ArrayList al = new ArrayList();
getCapacity( al );
al.add( "shailesh" );
getCapacity( al );
}
static void getCapacity( ArrayList<?> l )
throws Exception
{
Field dataField = ArrayList.class.getDeclaredField( "elementData" );
dataField.setAccessible( true );
System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}
Response: -
Size: 0, Capacity: 0
Size: 1, Capacity: 10
Artículo La capacidad predeterminada de ArrayList en Java 8 lo explica en detalle.
La pregunta es "¿por qué?".
Las inspecciones de perfiles de memoria (por ejemplo ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) muestran que las matrices vacías (llenas de nulos) ocupan toneladas de memoria.
El tamaño predeterminado de 10 objetos significa que asignamos 10 punteros (40 u 80 bytes) para la matriz subyacente en la creación y los rellenamos con valores nulos. Las aplicaciones java reales crean millones de listas de matrices.
La modificación introducida elimina ^ W posponga este consumo de memoria hasta el momento en que realmente utilizará la lista de matriz.
Si la primera operación que se realiza con una ArrayList es pasar
addAll
una colección que tiene más de diez elementos, entonces cualquier esfuerzo realizado para crear una matriz inicial de diez elementos para contener el contenido de la ArrayList sería arrojado por la ventana.
Cada vez que se agrega algo a una ArrayList es necesario probar si el tamaño de la lista resultante excederá el tamaño del almacén de respaldo;
permitir que el almacén de respaldo inicial tenga un tamaño cero en lugar de diez hará que esta prueba falle una vez más en la vida útil de una lista cuya primera operación es un "agregar" que requeriría crear la matriz inicial de diez elementos, pero ese costo es menos del costo de crear una matriz de diez elementos que nunca termina usándose.
Dicho esto, podría haber sido posible mejorar aún más el rendimiento en algunos contextos si hubiera una sobrecarga de "addAll" que especificaba cuántos elementos (si hubiera alguno) probablemente se agregarían a la lista después del presente, y cuáles podrían use eso para influir en su comportamiento de asignación. En algunos casos, el código que agrega los últimos elementos a una lista tendrá una muy buena idea de que la lista nunca necesitará más espacio. Hay muchas situaciones en las que una lista se completará una vez y nunca se modificará después de eso. Si en ese momento el código sabe que el tamaño final de una lista será de 170 elementos, tiene 150 elementos y una tienda de respaldo de tamaño 160, el crecimiento de la tienda de respaldo al tamaño 320 será inútil y dejarlo en el tamaño 320 o recortarlo a 170 será menos eficiente que simplemente hacer que la próxima asignación lo aumente a 170.
Técnicamente, es
10
, no cero, si admite una inicialización perezosa de la matriz de respaldo.
Ver:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
dónde
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
A lo que se refiere es solo al objeto de matriz inicial de tamaño cero que se comparte entre todos los objetos
ArrayList
inicialmente vacíos.
Es decir, la capacidad de
10
está garantizada
perezosamente
, una optimización que también está presente en Java 7.
Es cierto que el contrato del constructor no es del todo exacto. Quizás esta sea la fuente de confusión aquí.
Antecedentes
Aquí hay un correo electrónico de Mike Duigou
He publicado una versión actualizada del parche vacío ArrayList y HashMap.
http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/
Esta implementación revisada no introduce nuevos campos en ninguna de las clases. Para ArrayList, la asignación diferida de la matriz de respaldo se produce solo si la lista se crea con el tamaño predeterminado. Según nuestro equipo de análisis de rendimiento, aproximadamente el 85% de las instancias de ArrayList se crean con el tamaño predeterminado, por lo que esta optimización será válida para una abrumadora mayoría de los casos.
Para HashMap, se hace uso creativo del campo de umbral para rastrear el tamaño inicial solicitado hasta que se necesite la matriz de cubetas. En el lado de lectura, el caso de mapa vacío se prueba con isEmpty (). En el tamaño de escritura, se utiliza una comparación de (tabla == EMPTY_TABLE) para detectar la necesidad de inflar la matriz de cubetas. En readObject hay un poco más de trabajo para intentar elegir una capacidad inicial eficiente.
De: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html