Análise do princípio ArrayList

Pergunte

  1. Com base no que? Array? Lista vinculada? fila?
  2. Por que você pode manter o addelemento?

análise

Método para perceber

Variáveis ​​definidas:

Uma matriz é mantida:

transient Object[] elementData; // non-private to simplify nested class access

private int size;

ArrayListTudo internamente opera add、remove、set、getsobre elementDataessa matriz, então a ArrayListaplicação é baseada na matriz.

Dois comprimentos: size====> O comprimento da lista atual elementData.length====> Comprimento do
array Comprimento do array ≥ Comprimento da lista

O comprimento padrão e duas matrizes padrão:

/**
 * 默认的数组长度,当我们直接创建一个ArrayList对象时,容量为10
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 直接创建无参ArrayList时,内部指向该数组
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 创建带初始长度的ArrayList,或者传入另一个列表为参数创建对象时,如果长度为0或者传入列表长度为0,内部指向该数组
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

Método de construção

ArrayListTrês métodos de construção são definidos. Eles não são de construção de parâmetros. Um intmétodo de construção de parâmetros de tipo é passado e uma lista é passada como o método de construção de parâmetros.

  1. Passe o comprimento inicial. Este método é geralmente usado quando sabemos o comprimento da lista para evitar a aplicação de muito espaço de memória inútil
public ArrayList(int initialCapacity) {
    //如果长度大于0,则创建该长度的数组
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    //长度为0,指向默认数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
  1. Sem construção de parâmetro, irá elementDataapontar para a matriz padrão, comprimento 0
  2. Passe em uma lista. Arrays.copyOf()Copie a lista passada para a nova matriz por método e elementDataaponte para o endereço

Método de operação

A implementação interna de cada método é uma operação na matriz

get

Obtenha o valor do subscrito correspondente da matriz diretamente

public E get(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    return (E) elementData[index];
}

set

A mesma operação na matriz, substitua o valor correspondente ao subscrito na matriz pelo novo valor

public E set(int index, E element) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    E oldValue = (E) elementData[index];
    elementData[index] = element;
    return oldValue;
}

add

O subscrito é atual size+1, e o valor que precisa ser adicionado é definido como o valor correspondente ao subscrito na matriz. Como o comprimento da matriz é fixo, envolve a estratégia de expansão e crescimento mais importante, o ensureCapacityInternal(size + 1)método

public boolean add(E e) {
    //扩容算法,传入当前长度+1
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //扩容完成后赋值
    elementData[size++] = e;
    return true;
}

addAll

Método, primeiro converta o parâmetro de lista em um array e, em seguida, use o método System.arraycopy para copiar o array para elementDataele, o que também envolve a estratégia de expansão e crescimento, mas os parâmetros de entrada são diferentes .

public boolean addAll(Collection<? extends E> c) {
    //先转为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    //扩容算法,传入当前长度+需要add的元素的数量
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //拷贝数组
    System.arraycopy(a, 0, elementData, size, numNew);
    //列表长度修改
    size += numNew;
    return numNew != 0;
}

remove

A mesma operação de cópia é realizada na matriz, que é semelhante a mover cada elemento para frente um bit depois do elemento que precisa ser removido na matriz, sobrescrevendo o valor original.

public E remove(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    //数组拷贝
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

Expansão

Frente adde addAllmétodos, há ensureCapacityInternal1um método para algoritmos de expansão
olhar para a expansão de código relevante:

//①传入列表长度值
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
//②如果计算后需要的长度大于当前数组的长度,执行扩容操作
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//③对数组进行扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    //获取当前长度
    int oldCapacity = elementData.length;
    //计算新长度为旧长度 * 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果新长度小于需要的长度,使用传入的长度,适用于第一次创建后添加元素的扩容和addAll方法元素很多超过原有1.5倍的情况下
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果计算后长度大于最大列表长度
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //通过copyOf方法将原有数据拷贝到一个新的数组对象中,再赋值给elementData,至此,扩容完成。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

análise:

  1. Primeiro, passamos um valor, que representa o comprimento de nossa lista após add ou addAll
  2. Você não pode expandir a matriz toda vez que adicionar , caso contrário, o custo será muito alto, então a expansão da matriz não aumentará apenas o comprimento em um a cada vez.
  3. Se for um array vazio, pegue o máximo do valor padrão e o valor passado. A fim de criar uma matriz de comprimento 10 diretamente pela primeira vez, caso contrário, adicione um elemento para expandir uma vez, o que é caro
  4. Calcule se a expansão é necessária
  5. Operação de expansão, em circunstâncias normais, a capacidade da matriz é expandida em 1,5 vezes , dois casos especiais:
    1. Adicione o elemento pela primeira vez, expanda diretamente para o comprimento padrão de 10
    2. Existem muitos elementos addAll e o comprimento total é mais de 1,5 vezes o comprimento original, expanda diretamente para este comprimento
  6. Copie todos os dados originais para a nova matriz expandida por meio do método de cópia da matriz e , finalmente, atribua a matriz a elementData
  7. Expansão concluída

Resumindo

  1. ArrayList é implementado com base em uma matriz, o comprimento padrão da matriz é 10, todas as operações são operações na matriz mantida
  2. Os métodos add e addAll podem acionar a expansão da matriz
  3. Quando o comprimento não é suficiente, o comprimento de expansão é geralmente 1,5 vezes o comprimento existente
  4. Expansão, exclusão, etc. são obtidas copiando o array, então a lista não deve ser muito longa, caso contrário, cada cópia consumirá muito desempenho!

Acho que você gosta

Origin blog.csdn.net/lizebin_bin/article/details/88876878
Recomendado
Clasificación