概述:ArrayList是java的一个集合类,实现的是List接口。List接口下的实现类所表示的集合具有有序可重复的特点。下面通过对一段测试代码进行debug来分析add方法的工作原理以及ArrayList是怎么扩容的。
debug运行后在13行停住,此时我们step into 进入ArrayList的空参构造方法里去,看到以下代码:
这里是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,这两个是ArrayList中定义的两个空的对象数组
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
其中elementData就是添加元素的数组(从这可以知道ArrayList底层就是一个对象数组)。
接着我们再step over进入第一个for循环,我们重点关注第一次添加时和添加第十一个元素时add方法是怎么运行的。
当添加第一个元素时我们step into:
此时因为i是基本数据类型int,所以要进行装箱成Integer类型。这不是我们关注的重点,我们step out再次step into进入到add方法里:
可以看到,此时添加的元素e=1,而方法第一步执行的ensureCapacityInternal方法(该方法作用是在添加元素前判断数组是不是已经满了,如果是的话就会扩容):
这个方法调用一个ensureCapacityInternal方法,传入参数为calculateCapacity方法的返回值,进入calculateCapacity方法:
由于此时elementDate还是空数组,所以进入到第一个if语句中,返回DEFAULT_CAPACITY和mincapacity两者更大那个。前者是10,后者是size+1也就是1,所以返回的是10即mincapacity,接着进入到ensureExplicitCapacity方法中:
方法第一行表示修改次数modCount加一,接着判断第一个if语句是否成立,minCapacity=10,elementData.length=0,条件成立进入到if语句执行grow方法,这就是ArrayList的扩容方法:
第一步:将elementData数组大小赋给oldCapacity,表示数组以前的大小。
第二步:将oldCapacity的1.5倍赋给newCapacity,表示数组现在的大小。
但是,此时数组大小为0,扩容1.5倍还是0。
第三步:if语句条件成立,将minCapacity赋给newCapacity(为10),后两个if语句条件不成立,执行到方法的最后,方法的最后就是实现扩容的方法,调用Arrays的copyof方法,这个方法的作用是保留数组已有数组,增加数组的容量。
从这我们可以知道在第一次添加元素时,ArrayList会进行扩容,第一次扩容的大小为10。
在添加第2个一直到第10的元素时,
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
上述代码就不会执行了,因为数组已经不是空数组了,所以minCapacity每次就是size+1即数组在这次添加后已有元素个数
if (minCapacity - elementData.length > 0)
grow(minCapacity);
那么上述代码也就不会执行了,即不需要进行扩容。
一直让代码运行到第17行,也就是添加第十一个元素时:
首先进入到add方法中,流程还是和之前一样,我们直接跳到最关键的一步开始:
此时minCapacity(11) - elementData.length(10) > 0,所以要第二次进行扩容:
minCapacity表示已有元素个数包括此次添加的元素(因为是先判断再添加该元素),
它暗指最小需要多大容量的数组。
如果它比数组容量大的话,说明数组装不下,那么就需要进行扩容了。
还是和之前一样数组以前的大小赋给oldCapacity,将oldCapacity的1.5倍赋给newCapacity,表示数组现在的大小。和之前那次不同(之前那次oldCapacity=0,所以1.5倍相当于没变),这次不是0,此时newCapacity为oldCapacity的1.5倍=15,最后调用Arrays的copyof方法实现1.5倍扩容。