Alfabetización del conocimiento - Colección - El mecanismo de expansión de ArrayList

1. Método de inicialización

La mayor diferencia entre ArrayList y array es que ArrayList realiza una expansión dinámica y el tamaño de la matriz es fijo. La capa inferior de ArrayList se realiza por matriz.

imagen-20210321102453535

Echemos un vistazo a algunas de las variables de ArrayList.

	//默认大小
    private static final int DEFAULT_CAPACITY = 10;

	//空数组对象
    private static final Object[] EMPTY_ELEMENTDATA = {
    
    };

	//默认无参构造器的空数组,与EMPTY_ELEMENTDATA区分开是为了知道何时扩容了多少当第一个元素添加的时候
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

	//存放数据的缓存变量,在添加第一个元素的时候将被扩展为DEFAULT_CAPACITY
    transient Object[] elementData; // non-private to simplify nested class access

	//元素数量
    private int size;

ArrayList tiene tres métodos de inicialización


    public ArrayList(int initialCapacity) {
    
    
        if (initialCapacity > 0) {
    
    
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
    
    
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
    
    
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
    
    
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
    
    
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
    
    
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  • Si no se transfiere la capacidad inicial, se utilizará la capacidad predeterminada y se establecerá elementDataenDEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • Cuando se pasa la capacidad inicial, se juzgará si el valor de initialCapacity es mayor que 0. Si es mayor que 0, se crea una nueva matriz, y si es igual a 0, se configura directamente

EMPTY_ELEMENTDATA

  • Pase una colección, primero llame a toArray para asignarle elementData, y también juzgue si su longitud es 0, si es 0, establézcalo elementDataen EMPTY_ELEMENTDATA.

Dos, expansión dinámica

El primero es llegar al método add

public boolean add(E e) {
    
    
    //确保容量足够
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //添加元素
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    
    
    // 判断elementData是不是默认的空数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
        // 取得两个参数中的最大值:DEFAULT_CAPACITY --> 10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    
    
    //记录变更的次数与线程的安全性有关
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

Cuando la capacidad minCapacity requerida actual es mayor que elementData.length, debe expandirse, es decir, el método central de toda la expansión está en crecimiento.

private void grow(int minCapacity) {
    
    
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //新的容量大小为:之前容量的大小 + (之前容量的大小 / 2) 注:“>>”的意思为除以2的1次方
    int newCapacity = oldCapacity + (oldCapacity >> 1);
     // 扩容后的容量小于前容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 扩容后的容量大于arraylist最大容量时
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

Primero, averigüe el significado de las tres variables clave:

  • minCapacity: la capacidad mínima requerida para esta expansión
  • oldCapacity: la capacidad original de la matriz antes de la expansión
  • newCapacity: capacidad estimada después de la expansión

La mayor duda aquí es newCapacity-MAX_ARRAY_SIZE

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

El valor de MAX_ARRAY_SIZE es Integer.MAX_VALUE-8. Si lees mi principio de implementación de bloqueo sincronizado, debe quedar claro que la máquina virtual almacenará un tamaño de matriz adicional de 32 bits o 8 bytes en el encabezado del objeto para el objeto de matriz, entonces esto se reduce a las 8. Puede entender

longitud contenido Descripción
32/64 bits Marcar palabra Almacene el código hash o la información de bloqueo del objeto
32/64 bits Dirección de metadatos de clase Puntero para almacenar datos de tipo de objeto
32/32 bits Longitud de la matriz La longitud de la matriz

Luego baja para perseguir el método hugeCapacity

private static int hugeCapacity(int minCapacity) {
    
    
    //溢出了 则变为负数
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

Descubrí que se hizo un juicio aquí: minCapacity> MAX_ARRAY_SIZE, lo que significa que si minCapacity (la capacidad actual requerida) es mayor que MAX_ARRAY_SIZE, entonces la capacidad de la matriz se asigna directamente a Integer.MAX_VALUE? Entonces, no quise decir que la capacidad máxima de la matriz sea Integer.MAX_VALUE-8. ¿Cómo podría copiarse esto directamente? Tenga en cuenta que la interpretación del código fuente de MAX_ARRAY_SIZE dice que hay algunas máquinas virtuales. Si minCapacity es mayor que MAX_ARRAY_SIZE en este momento, la máquina virtual se ignorará y se asignará directamente a Integer.MAX_VALUE

Tres, mecanismo de falla rápido

Las colecciones en Java proporcionan un mecanismo de error llamado fail-fast, que solo se puede usar para detectar errores. Cuando varios subprocesos operan en el contenido de la misma colección, puede ocurrir un mecanismo de fail-fast.

Por ejemplo, llamar al método remove durante el proceso de iteración arrojará ConcurrentModificationException

public class FastFailTest {
    
    
    public static void main(String[] args) {
    
    
        // 构建ArrayList
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        for (int i : list) {
    
    
            System.out.println(i);
            list.remove(1);
        }
    }
}

El método anterior resume el uso del método remove en el proceso de iteración y generará una excepción. La razón es que el valor de modCount se verificará durante el proceso de iteración, pero llamar al método remove causará modCount ++, que arrojará un excepción al juzgar que los dos modCounts son diferentes.

El enfoque correcto debería ser utilizar el método de eliminación del iterador, ya que el uso del método de eliminación del iterador modificará el valor de esperabaModCount para que no se active el mecanismo de falla rápida.

En un entorno de alta concurrencia, ArrayList no es seguro para subprocesos y activará el mecanismo ail-fast. Para soluciones específicas, consulte la clase de seguridad de subprocesos múltiples desde la entrada hasta la clase de seguridad de colección avanzada (4) en la lista de entornos de subprocesos múltiples

Supongo que te gusta

Origin blog.csdn.net/weixin_44706647/article/details/115046132
Recomendado
Clasificación