参考资料:JDK 8 中 ArrayList 的自动扩容 要详细了解序列化,可以参考该博客:Java 中的 transient 关键字
先说结论

源码解读
我们知道数组本身是无法直接扩容的,要想存储更多的数据需要新建一个数组,再把之前数组的所有内容填充进去。而 ArrayList 的自动扩容机制,使得其在处理变长度数组的时候非常方便。
字段
在了解扩容机制之前,先对 ArrayList 中的各个字段进行解读:
// 用于支持 Serializable 机制,与扩容无关private static final long serialVersionUID = 8683452581122892189L;// 默认的扩容量的长度为10private static final int DEFAULT_CAPACITY = 10;// 在类加载时就定义的两个空元素数组private static final Object[] EMPTY_ELEMENTDATA = new Object[0];private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];// ArrayList 中元素存储的底层实现,本质是一个Object数组transient Object[] elementData;// 存储当前 ArrayList 中元素的数量private int size;
可以看到,本质上 ArrayList 底层用于存储元素的字段是一个 Object 数组,这个数组使用了 transient 关键字修饰,直译是“暂时的,临时的”,在 Java 中的含义是不可序列化的,即在程序运行过程中该变量只会存在于调用者的内存中,而不会写入磁盘。
构造器
接下来查看 ArrayList 是怎么初始化的, 在 ArrayList 类中给出了如下三个构造器:
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else {if (initialCapacity != 0) {throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);}this.elementData = EMPTY_ELEMENTDATA;}}public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}public ArrayList(Collection<? extends E> c) {Object[] a = c.toArray();if ((this.size = a.length) != 0) {if (c.getClass() == ArrayList.class) {this.elementData = a;} else {this.elementData = Arrays.copyOf(a, this.size, Object[].class);}} else {this.elementData = EMPTY_ELEMENTDATA;}}
可以看到,第一个和第三个构造器调用的时候,会按照所传参数初始化 elementData 数组,数组大小与所传参数有关。当传入的参数小于等于0(调用第一个构造器)或传入了一个空的 Collection 对象(调用第三个构造器)时,elementData 数组会被初始化为 EMPTY_ELEMENTDATA 数组,它是一个空的 Object 数组对象。而在调用默认构造器的时候,elementData 数组会被初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 数组,它也是一个空的 Object 数组对象。
add()
当我们向 ArrayList 对象添加元素的时候,可能会发生 elementData 数组原本的长度不足,这时候就应该触发自动扩容机制,所以接下来查看 add() 方法是如何处理的。
private void add(E e, Object[] elementData, int s) {// 触发自动扩容机制if (s == elementData.length) {elementData = this.grow();}elementData[s] = e;this.size = s + 1;}public boolean add(E e) {++this.modCount;this.add(e, this.elementData, this.size);return true;}public void add(int index, E element) {this.rangeCheckForAdd(index);++this.modCount;int s;Object[] elementData;// 触发自动扩容机制if ((s = this.size) == (elementData = this.elementData).length) {elementData = this.grow();}System.arraycopy(elementData, index, elementData, index + 1, s - index);elementData[index] = element;this.size = s + 1;}
可以看到,如果直接插入某一元素,会在内部调用私有的 add() 方法,该方法会将 size 字段的值和 elementData 数组的大小进行比较,当二者相等,也即 elementData 数组存满了的时候,就会调用 grow() 方法。同样地,如果在某一个索引处添加某个元素,也会触发这一机制。
grow()
接下来考察 grow() 方法是如何实现的,源码如下所示:
private Object[] grow(int minCapacity) {int oldCapacity = this.elementData.length;if (oldCapacity <= 0 && this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return this.elementData = new Object[Math.max(10, minCapacity)];} else {int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1);return this.elementData = Arrays.copyOf(this.elementData, newCapacity);}}private Object[] grow() {return this.grow(this.size + 1);}
当外部调用无参的 grow() 方法的时候,该方法会往有参的 grow() 方法中传入一个参数,该参数为目前 ArrayList 中实际存储的元素数量 +1,表示本次扩容所需的最小容量。因此可以想见,对于 addAll() 这样的一次插入多个元素的方法而言,其会直接调用有参的 grow() 方法,实际也正是如此。
public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();++this.modCount;int numNew = a.length;if (numNew == 0) {return false;} else {Object[] elementData;int s;// 触发扩容机制if (numNew > (elementData = this.elementData).length - (s = this.size)) {elementData = this.grow(s + numNew);}System.arraycopy(a, 0, elementData, s, numNew);this.size = s + numNew;return true;}}public boolean addAll(int index, Collection<? extends E> c) {this.rangeCheckForAdd(index);Object[] a = c.toArray();++this.modCount;int numNew = a.length;if (numNew == 0) {return false;} else {Object[] elementData;int s;// 触发扩容机制if (numNew > (elementData = this.elementData).length - (s = this.size)) {elementData = this.grow(s + numNew);}int numMoved = s - index;if (numMoved > 0) {System.arraycopy(elementData, index, elementData, index + numNew, numMoved);}System.arraycopy(a, 0, elementData, index, numNew);this.size = s + numNew;return true;}}
在有参的 grow() 方法中,先读取扩容之前的 elementData 数组的长度,如果该数组之前长度就为0并且就是字段中对应的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 数组(即调用的是无参构造器),就将 elementData 数组扩容到需求的最小长度,如果这个最小长度比10小,那就会直接扩容到10;而如果该数组之前长度不为0,或者初始化的时候调用的不是无参构造器,那么就会调用 ArraySupports 类中的 newLength 方法来获得需要增长的长度,并返回扩容后新的 elementData 数组。newLength 方法是这样定义的:
// 调用语句ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1);public static int newLength(int oldLength, int minGrowth, int prefGrowth) {int newLength = Math.max(minGrowth, prefGrowth) + oldLength;return newLength - 2147483639 <= 0 ? newLength : hugeLength(oldLength, minGrowth);}
该方法有三个参数,第一个参数代表之前容量,第二个参数代表最少应该增加的容量,第三个参数代表优先考虑增加的容量,最终增加的容量大小会根据后两者中的较大值来确定,最后还会添加一个判断来保证返回值不会溢出。
而 grow() 方法中传入的第三个参数为过去的值按位右移1位,可以理解为整型计算中的除以2。至此我们可以得到整个自动扩容的流程,将其整理为流程图为:
