Pergunte
- Com base no que? Array? Lista vinculada? fila?
- Por que você pode manter o
add
elemento?
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;
ArrayList
Tudo internamente opera add、remove、set、get
sobre elementData
essa matriz, então a ArrayList
aplicaçã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
ArrayList
Três métodos de construção são definidos. Eles não são de construção de parâmetros. Um int
método de construção de parâmetros de tipo é passado e uma lista é passada como o método de construção de parâmetros.
- 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);
}
}
- Sem construção de parâmetro, irá
elementData
apontar para a matriz padrão, comprimento 0 - Passe em uma lista.
Arrays.copyOf()
Copie a lista passada para a nova matriz por método eelementData
aponte 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 elementData
ele, 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 add
e addAll
métodos, há ensureCapacityInternal1
um 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:
- Primeiro, passamos um valor, que representa o comprimento de nossa lista após add ou addAll
- 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.
- 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
- Calcule se a expansão é necessária
- Operação de expansão, em circunstâncias normais, a capacidade da matriz é expandida em 1,5 vezes , dois casos especiais:
- Adicione o elemento pela primeira vez, expanda diretamente para o comprimento padrão de 10
- Existem muitos elementos addAll e o comprimento total é mais de 1,5 vezes o comprimento original, expanda diretamente para este comprimento
- 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
- Expansão concluída
Resumindo
- 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
- Os métodos add e addAll podem acionar a expansão da matriz
- Quando o comprimento não é suficiente, o comprimento de expansão é geralmente 1,5 vezes o comprimento existente
- 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!