「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
通过注释描述,可以大致了解到,ArrayList 具有自动扩容机制。扩容触发条件是:
- 向 ArrayList 添加元素时
- 调用
ensureCapacity方法
下面时 ArrayList 添加元素的 add方法
public boolean add(E e) {
// size 是当前元素个数(需要与容量进行区分)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
其中调用了ensureCapacityInternal方法,与ensureCapacity方法名类似,Internal 可以大致猜测是因为其为私有方法。
下面对ensureCapacityInternal和ensureCapacity进行简单的分析,其代码如下:
简单解释以下其中的属性:
-
transient Object[] elementData;ArrayList 中实际存储数据的数组的缓冲区,扩容操作也是操作该数组
-
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};一个空数组
-
private static final int DEFAULT_CAPACITY = 10;默认的初始容量大小
// minCapacity 为所需的最小容量
public void ensureCapacity(int minCapacity) {
// 验证 elementData 是否为默认创建的数组
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA 与构造方法有关,可见附录
// 无参构造的 ArrayList 默认扩展大小为 10,否则最小为 0
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 若是无参构造的 ArrayList ,已有默认容量(为10)
// 当所需扩容容量小于默认容量,则仍未默认容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
发现两者都调用了ensureExplicitCapacity方法,此处未见扩容的操作,于是继续看ensureExplicitCapacity方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 判断是否需要执行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount用于记录 ArrayList 当前修改次数,对扩容影响不大。下面对扩容后的容量和当前的长度做对比,符合则执行 grow 方法进行扩容。
见下方代码,可以看出主要的扩容动作在grow方法中实现。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 下面的位运算扩容大小想当于 oldCapacity * 1.5:使用位操作提高效率
// 对下面判断条件概括一下:
// 默认扩容 1.5 倍
// 若扩容 1.5 倍后 小于 所需扩容大小(minCapacity)则不容扩容 1.5 倍就够用,新容量为 minCapacity
// 若超出最大容量,则进行一些处理,后面已经给出代码
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总结
一方面,开发者可以通过ensureCapacity进行指定大小的扩容。
另一方面,是 ArrayList 内部的扩容,拿 add方法举例:
- 首先会调用扩容方法,因为插入元素,需要的容量大小为 size + 1
- 在
ensureCapacityInternal方法中判断所需扩容大小(通过空数组判断是否存在默认容量) - 在
ensureExplicitCapacity中判断是否需要扩容 - 在
grow中进行扩容操作
ArrayList 中元素数量和容量分离,同时扩容操作时会对扩容大小进行多方面的判断操作,具有下面的优点:
- 元素数量 与 容量 分离:在尽量少触发扩容的情况下实现自动扩容
- 扩容大小判断:保证扩容大小能够不占用大量空间
- 采用 位运算:提高运算效率
附录
ArrayList 构造方法
ArrayList 构造方法如下三种:
- 无参:创建默认容量大小的 ArrayList
(int initialCapacity):创建指定容量大小的 ArrayListArrayList(Collection<? extends E> c):根据传入的集合进行创建
若传入的集合长度为 0 或者指定容量为 0 进行创建,elemenData 是内部 private static final Object[] EMPTY_ELEMENTDATA = {};引用的空数组。
若无参构造,elemenData 是内部 DEFAULTCAPACITY_EMPTY_ELEMENTDATA引用的空数组。
两个空数组:
EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA从注释上来看,其实主要是用于扩容时的区分,我的想法如下:
因为有参传入时,已经有实际的容量大小,如果为 0 ,此时 ArrayList 的容量也为 0 。
而使用无参传入时,虽然没有数据,但是默认容量是 10。
在上面的扩容分析中,也可以看到扩容判断用的为
DEFAULTCAPACITY_EMPTY_ELEMENTDATA。因为无参构造时虽然没有数据,但已有默认容量,可以判断无参构造的 ArrayList 添加元素时是否触发扩容。
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) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
\