阅读源码了解ArrayList的扩容机制
首先要知道ArrayList类内定义的几个静态常量,这些常量会在初始化和扩容时使用。
说明:对于Capacity我更愿意使用“容纳容量”来说明这是能够容纳的最大数目,容量一词在ArrayList中有时太容易使人误解了。
//默认容纳容量
private static final int DEFAULT_CAPACITY = 10;
//默认空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容纳容量下的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数据的数组,定义为Object是为了容纳泛型
transient Object[] elementData;
//数组存储的实际内容大小
private int size;
//最大数组大小,数组为int类型的最大值-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Capacity和size意义不同。
如果以装鸡蛋的篮子比喻,Capacity指的是篮子最多可以容纳多少鸡蛋,而size是篮子中现有鸡蛋的真正数目。
ArrayList有三个构造方法,也需要有初步的了解,这样才能对ArrayList的扩容机制有更多的了解。
//指定容纳容量创建ArrayList
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { //初始化容纳容量大于0
this.elementData = new Object[initialCapacity]; //数组初始化容纳容量为指定的容纳容量
} else if (initialCapacity == 0) { //初始化容纳容量为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) { //判断长度是否为0
if (c.getClass() == ArrayList.class) { //判断是否为ArrayList
elementData = a; //赋值数组
} else {
elementData = Arrays.copyOf(a, size, Object[].class); //调用方法赋值数组
}
} else {
elementData = EMPTY_ELEMENTDATA; //长度为0初始化为默认空数组
}
}
通过上述构造方法可知,无参时会创建空数组,有参时会根据参数类型创建对应的数组。下面以添加1个元素的add方法来说明ArrayList的具体扩容机制。
public boolean add(E e) {
//确认容纳容量可用,传入参数为当前数组内容加1
ensureCapacityInternal(size + 1);
//设置对应位置数组内容为传入内容,然后数组内容自增1
elementData[size++] = e;
return true;
}
add方法先调用ensureCapacityInternal方法来保证容纳容量可用,随后在指定的数组位置赋值为传入参数,随后size自增1来添加1个元素。
然后看下ensureCapacityInternal方法。
private void ensureCapacityInternal(int minCapacity) {
//计算容量然后判断是否需要扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这里可以看到这个方法里面调用了很多方法,由内向外看一下具体方法的作用。
先看calculateCapacity方法。
//计算容量,字面意思令人一头雾水,细究
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断数组是否为默认容纳容量下的空数组(实际上数组如果为默认空数组结果也是一样的,因为二者实际内容相同)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//是则返回默认容量和传入的最小容量两者之间的较大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//否则返回最小容量
return minCapacity;
}
阅读源码可知calculateCapacity方法实际上是判断数组是否为空然后计算其对应的容量,若数组不为空则直接返回传入的最小容量。
然后来看ensureExplicitCapacity方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判断是否需要扩容,这里的elementData.length可以认为是容纳容量
if (minCapacity - elementData.length > 0)
//调用方法进行扩容
grow(minCapacity);
}
终于到了真正的扩容方法了,一探究竟
private void grow(int minCapacity) {
//将数组容纳容量赋值给oldCapacity
int oldCapacity = elementData.length;
//将数组容纳容量扩至原来的1.5倍的值赋给newCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断newCapacity和minCapacity的大小关系
if (newCapacity - minCapacity < 0)
//newCapacity小于minCapacity则将minCapacity的值赋给minCapacity
newCapacity = minCapacity;
//判断newCapacity和最大数组大小的大小关系
if (newCapacity - MAX_ARRAY_SIZE > 0)
//调用hugeCapacity方法,传入参数为minCapacity,将得到的值赋给newCapacity
newCapacity = hugeCapacity(minCapacity);
//调用Arrays.copyOf方法扩容数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
看hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
//判断minCapacity是否小于0,小于0证明此时数组内容已经达到int的最大容量,+1后溢出为负值了
if (minCapacity < 0)
//抛出错误
throw new OutOfMemoryError();
//返回值为判断minCapacity是否大于最大数组大小,大于返回int的最大值,不大于返回最大数组大小
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
由以上可知,ArrayList在调用构造方法时传入int参数时数组小于10是没有实际意义的,因为ArrayList在calculateCapacity会将容纳容量改为默认容量,而在插入1个新的元素时,会先判断数组是否为空,为空则会判断默认数组大小和当前数组内容数量加1之间的大小关系返回其中的较大值,然后将其和数组长度(即为前文的容纳容量)的大小做比较,然后决定是否扩容,若扩容则会将原来容纳容量的1.5倍数值与最小所需容量做对比,再综合最大容量与原来容纳容量的1.5倍数值的大小关系结合对比得出结果,根据结果对原数组进行扩容。
在ArrayList中还提供了手动扩容和缩减容量至最小值的方法。
手动扩容的方法
public void ensureCapacity(int minCapacity) {
//数组为空,minExpand赋值为0,不为空赋值为默认大小
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
//判断minCapacity和minExpand的大小关系
if (minCapacity > minExpand) {
//确认是否需要扩容
ensureExplicitCapacity(minCapacity);
}
}
缩减容量至最小值的方法
public void trimToSize() {
modCount++;
//如果数组内容数目大于数组容纳容量
if (size < elementData.length) {
//若数组内容为空,缩减为空数组,否则缩减至数组内容大小
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
以上是对ArrayList扩容机制的探寻,这只是沧海一粟,详细的内容仍需细读源码方可了解。