ArrayList是java中最常用的集合之一,底层的数据结构是数组,在学习集合时,阅读源码是必不可少的环节之一,阅读源码可以有效的帮助我们深入了解其工作原理,下面根据源码详细的介绍下扩容机制,环境为jdk1.8。
首先看看它的无参构造,ArrayList无参构造方法如下:
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
先来看看elementData,这是一个Object数组,也就是集合中的元素,其定义如下:
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个final的Object空数组,定义如下:
/** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
由注释我们可知这是一个共享的空数组,用于默认大小的空实例。
那么ArrayList什么时候开始扩容呢,从add()方法中可以找到答案,源码如下:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { // 检查是否需要扩容 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
很明显第一句代码就是其扩容检查方法,参数是当前的集合大小+1,也就是新增元素后的数组实际大小。继续跟进ensureCapacityInternal()方法:
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
此处的minCapacity就是需要的最少空间。继续跟进calculateCapacity()方法代码:
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
由此可以知道:如果集合中的元素是默认的空数组的话,那么将会返回DEFAULT_CAPACITY与minCapacity两者的较大值。DEFAULT_CAPACITY定义如下:
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;
所以默认的初始容量是10。再进到ensureExplicitCapacity()方法中来看:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code // 如果需要的最少空间大于目前的数组长度,则需要扩容 if (minCapacity - elementData.length > 0) // 扩容方法 grow(minCapacity); }
这里的modCount是指数组结构被修改的次数,从代码中可以看到如果需要的最小空间大于当前集合的长度的话就会调用grow()方法,方法定义如下:
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 右移等于除2,也就是说新容量等于旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 这里会判断扩容后的容量是否会溢出 // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 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); }
此处的grow()方法就是最终的扩容方法,if (newCapacity - MAX_ARRAY_SIZE > 0)这个判断就很有意思了,可能有人会想为什么不写成if (newCapacity > MAX_ARRAY_SIZE)呢?他们表达的是同一个意思吗?答案:不是。因为如果扩容后newCapacity溢出的话,那么它就会变成负数,前者的判断就会返回true,后者则会返回false,两者相反。
再继续看如果溢出会怎么样:
private static int hugeCapacity(int minCapacity) { // 小于0说明是负数,所以判断为溢出,此时抛出内存溢出错误 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
由源代码可知:如果需要的最小空间大于最大的数组空间的话(前面说过最大数组空间)那么新的数组大小就是Integer的最大值,否则为最大数组空间。
总结:
当每次添加新元素时,ArrayList都会检查是否需要扩容,如需扩容,会扩容成当前容量的1.5倍,如果溢出(负数)的话则会抛出OutOfMemoryError,如果介于Integer.MAX_VALUE和MAX_ARRAY_SIZE两者之间,则取Integer.MAX_VALUE。