从源码角度解析ArrayList扩容的原理
1、ArrayList的构造方法
看源码先看构造方法,我们首先看看ArrayList
的构造方法,它有三个构造方法:
public ArrayList(int initialCapacity); //传入一个数组的大小
public ArrayList(); //空参构造
public ArrayList(Collection<? extends E> c); //传入一个Collection集合
对于这三个构造方法,我们分别来看看它的源码吧,我们首先来看看第一个:
public ArrayList(int initialCapacity) {
//判断大小是否大于0
if (initialCapacity > 0) {
//大于0则新建一个大小为initialCapacity的Object数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果等于0则给它赋值一个空的数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//小于0则抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
对于这个构造方法,首先当initialCapacity
大于0时,就会去创建一个大小为initialCapacity
的Object数组。如果等于0,那就会给数组赋EMPTY_ELEMENTDATA
,我们可以来看看这个EMPTY_ELEMENTDATA
是什么东西:
private static final Object[] EMPTY_ELEMENTDATA = {
};
其实这个EMPTY_ELEMENTDATA
就是一个空的数组。
再来看看无参的构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这个无参的构造方法直接给数组赋了一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,其实这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA
也是一个空数组,我们可以看看源码里面对该常量的定义:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
对于第三个构造方法,就是把Collection
转化为一个数组而已:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
2、ArrayList的add方法
上面看完了构造方法后,我们知道了如果初始化ArrayList
时是空参或者传入的大小为0时,就会初始化一个空的数组 ;如果大于0则给它新建一个对应大小的Object
数组,接下来我们来看看add(E e)
方法吧
public boolean add(E e) {
//扩容关键的一步
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这个方法里面比较关键的就是这个ensureCapacityInternal()
方法了,后面的步骤也就是给他赋值而已,没什么关键的。所以我们来看看这个ensureCapacityInternal(size+1)
方法吧。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这个方法调用了两个方法,一个是calculateCapacity(Object[] elementData, int minCapacity)
,另一个是ensureExplicitCapacity
,我们先来看看括号内的那个calculateCapacity(Object[] elementData, int minCapacity)
方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//private static final int DEFAULT_CAPACITY = 10;
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//不为空则返回minCapacity
return minCapacity;
}
我们可以看到这个方法是计算数组容量的一个方法,传入的minCapacity
是一个add()
方法里面传入的插入数组后的大小。当数组为空时(就是我们前面传入空参或者为0时)就Math.max(默认大小, minCapacity)
,即使取默认大小与数组所需的最小容量的最大值。;如果不为空则直接返回了。
我们再看看ensureExplicitCapacity
方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果数组所需的最小容量 > 数组的长度
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
可以看到如果数组所需的最小容量大于数组的长度的时候,ArrayList
就会扩容了,再看看grow
方法:
private void grow(int minCapacity) {
// 获取当前数组的长度
int oldCapacity = elementData.length;
//oldCapacity >> 1 = oldCapacity/2,即使oldCapacity + oldCapacity/2。
//增加了原来的一半,即扩容后是原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后还小于所需的容量
if (newCapacity - minCapacity < 0)
//直接将所需容量复制给新的大小
newCapacity = minCapacity;
//如果扩容后的大小大于最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 把老数组copy到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
所以在这个扩容函数里面主要就是有三步:
- 增加了原来的一半,即扩容后是原来的1.5倍
- 如果扩容后还小于所需的容量,那么直接把容量变为所需的最小容量
- 把老数组copy到新数组中
3、总结
总结来说就是:
- 初始化
ArrayList
时,如果指定了初始的大小,那么就初始化相应大小的Object
数组;如果没有指定大小,就初始化空数组,默认大小是10。 - 扩容时,增加了原来的一半,即扩容后的大小是原来的1.5倍。