¿Por qué el uso de diferentes constructores ArrayList causa una tasa de crecimiento diferente de la matriz interna?

Eugene:

Me parece a tropezar con algo interesante en ArrayListla aplicación que no puedo envolver mi cabeza alrededor. Aquí hay un código que muestra lo que se refiere a:

public class Sandbox {

    private static final VarHandle VAR_HANDLE_ARRAY_LIST;

    static {
        try {
            Lookup lookupArrayList = MethodHandles.privateLookupIn(ArrayList.class, MethodHandles.lookup());
            VAR_HANDLE_ARRAY_LIST = lookupArrayList.findVarHandle(ArrayList.class, "elementData", Object[].class);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    public static void main(String[] args) {

        List<String> defaultConstructorList = new ArrayList<>();
        defaultConstructorList.add("one");

        Object[] elementData = (Object[]) VAR_HANDLE_ARRAY_LIST.get(defaultConstructorList);
        System.out.println(elementData.length);

        List<String> zeroConstructorList = new ArrayList<>(0);
        zeroConstructorList.add("one");

        elementData = (Object[]) VAR_HANDLE_ARRAY_LIST.get(zeroConstructorList);
        System.out.println(elementData.length);

    }
}

La idea es que si se crea una ArrayListcomo esta:

List<String> defaultConstructorList = new ArrayList<>();
defaultConstructorList.add("one");

Y la mirada al interior de lo que el elementData( Object[]donde se guardan todos los elementos) de la que informará 10. De este modo se agrega un elemento - se obtiene 9 ranuras adicionales que son no-usado.

Si, por el contrario, lo hace:

List<String> zeroConstructorList = new ArrayList<>(0);
zeroConstructorList.add("one");

agrega un elemento, espacio reservado es sólo para ese elemento , nada más.

Internamente esto se logra a través de dos campos:

/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 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 = {};

Cuando se crea una ArrayListvía new ArrayList(0)- EMPTY_ELEMENTDATAse utilizará.

Cuando se crea una ArrayListvía new Arraylist()- DEFAULTCAPACITY_EMPTY_ELEMENTDATAse utiliza.

La parte intuitiva de mi interior - simplemente grita "eliminar DEFAULTCAPACITY_EMPTY_ELEMENTDATA" y dejar que todos los casos ser manejados con EMPTY_ELEMENTDATA; por supuesto, el comentario de código:

Distinguimos esto desde EMPTY_ELEMENTDATA saber cuánto se infle cuando se añade primer elemento

hace tiene sentido, pero ¿por qué uno de inflar 10(mucho más de lo que pedí) y el otro a 1(exactamente tanto como solicité).


Incluso si se utiliza List<String> zeroConstructorList = new ArrayList<>(0), y seguir añadiendo elementos, con el tiempo se llega a un punto en que elementDataes más grande que la solicitada:

    List<String> zeroConstructorList = new ArrayList<>(0);
    zeroConstructorList.add("one");
    zeroConstructorList.add("two");
    zeroConstructorList.add("three");
    zeroConstructorList.add("four");
    zeroConstructorList.add("five"); // elementData will report 6, though there are 5 elements only

Sin embargo, la velocidad a la que crece es más pequeño que el caso del constructor por defecto.


Esto me recuerda sobre HashMapla aplicación, donde el número de cubos es casi siempre más de lo que pidió; pero no lo que se hace debido a la necesidad de "poder de dos" cubos necesaria, no es el caso aquí, sin embargo.

Así que la pregunta es - ¿Puede alguien explicar esta diferencia para mí?

Holger:

Se obtiene exactamente lo que solicitó, correspondiente lo que se ha especificado, incluso en las versiones anteriores, en los que la ejecución era diferente:

ArrayList()

Construye una lista vacía con una capacidad inicial de diez.

ArrayList(int)

Construye una lista vacía con la capacidad inicial especificada.

Por lo tanto, la construcción de la ArrayListcon el constructor por defecto le dará una ArrayListcon una capacidad inicial de diez años, por lo que siempre que el tamaño de la lista es de diez o más pequeña, siempre será necesaria ninguna operación de cambio de tamaño.

Por el contrario, el constructor con el intargumento utilizará precisamente la capacidad especificada, sujeto a la política de crecimiento que se especifica como

Los detalles de la política de crecimiento no se especifican más allá del hecho de que la adición de un elemento tiene coste amortizado tiempo constante.

el cual se aplica incluso cuando se especifica una capacidad inicial de cero.

Java 8 añadió la optimización que la creación de la matriz de elementos de diez se pospone hasta que se añade el primer elemento. Esto está abordando específicamente el caso común que ArrayListinstancias (creados con la capacidad por defecto) permanecen vacío durante un largo tiempo, o incluso toda su vida. Además, cuando la primera operación real es addAll, podría omitir la primera operación de cambio de tamaño gama. Esto no afecta a las listas con una capacidad inicial explícita, como los que suelen ser elegidos cuidadosamente.

Como se indica en esta respuesta :

De acuerdo con nuestro equipo de análisis de rendimiento, aproximadamente el 85% de los casos de ArrayList se crean al tamaño por defecto por lo que esta optimización será válido por una abrumadora mayoría de los casos.

La motivación era precisamente para optimizar estos escenarios, no tocar la capacidad predeterminado especificado, que se definió atrás cuando ArrayListfue creado. (Aunque JDK 1.4 es la primera que especifica explícitamente)

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=120774&siteId=1
Recomendado
Clasificación