ArrayList扩容原理
众所周知,ArrayList是底层是使用数组这一数据结构来实现的,其扩容也是在数组的基础上扩容。从jdk8
源码的角度来分析其扩容机制。
先看一下ArrayList的结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* 默认初始化容量=10.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享的无参构造使用的空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 共享的有参构造使用的空数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 实际存放数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 长度
*/
private int size;
/**
* 数组的最大容量 = Integer最大值-8
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 有参数的构造方法
*/
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);
}
}
}
这里说一下比较多的无参数的add
方法,添加之前,先检查list内数组的容量,同时传一个 当前容量+1 作为添加后的最小容量。
/**
* 无index参数时,默认追加到list结尾.
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
再看这个方法的实现:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
首先计算一下数据容量,如果之前是空数组,则初始化容量等于10.
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 之前是空数组(地址值比较)时,第一次添加元素会初始化一个size=10的数组。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 其他情况返回最小容量
return minCapacity;
}
确定完期望的最小数组容量以后,然后检查是否需要扩容。
private void ensureExplicitCapacity(int minCapacity) {
// modCount 是AbstractList里的记录底层数据结构变更的次数,是给迭代器使用的。
modCount++;
// 如果要求的最小容量 比当前数组的容量大,就需要扩容。
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
该方法中,如果最小容量比当前的数据容量(第一次初始化为10)大,会调用grow
方法进行扩容。
private void grow(int minCapacity) {
// 扩容代码
int oldCapacity = elementData.length;
// 新容量 = 旧容量 + 旧容量的一半(右移一位),即1.5倍。第一次扩容后容量=15
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新容量比最小容量小时,新容量=最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 新容量比 数组最大容量大时,调用 hugeCapacity,可能会抛OOM,一般不会有这么大的容量。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity一般等于size+1
elementData = Arrays.copyOf(elementData, newCapacity);
}
确定完新容量以后,就调用操作系统提供的数组native
的Copy方法,数组的指针是一块连续的内存地址。好处是操作系统直接从一个地址开始复制若干字节到新地址,比使用java方法遍历复制快的多。
同理,通过index进行add的方式,比add多了一个index之后的数据的复制操作。
/**
* 有index参数时,在指定位置插入
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}