arraylist扩容机制

ArrayList扩容机制分析~

一、从ArrayList的构造函数说起

1.1、ArrayList有三种方式初始化,构造方法的源码如下:


* 默认初始容量大小为10
*/
private static final int DEFAULT_CAPACITY = 10;

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


* 默认构造函数,使用初始容量10构造一个空列表
*/
public () {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


* 带初始容量的构造函数(构造者自己指定容量)
*/
public (int initialCapacity) {
//初始容量大于0
if(initialCapacity > 0){
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
}else if(initialCapacity == 0 ){
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
}else { //若初始容量小于0,则抛出异常
throw new IllegalArgumentException("Illeagl Capacity:" + initialCapacity); }
}


*构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回,
*如果指定的集合为null,throw NullPointerException
*/
public (Collection<? extends E> c) {
elementData = c.toArray();
if((size =elementData.length) != 0){
if(elementData.getClass() != Object[].class)
elementData = Arrays.copyof(elementData,size,Object[].class);
} else {
this.elementData = EMPTY.ELEMENTDATA;
}
}

从以上三种构造创建ArrayList对象可知:

以无参构造方法创建ArrayList时,实际上初始化赋值的是一个空数组,真正对数组进行添加元素操作时,才是真正分配容量,即向数组中添加第一个元素时,数组容量扩为10

二、ArrayList扩容机制分析

1、add()方法


* 将指定元素追加到此列表的末尾
*/
public boolean add(E e){
//添加元素之前,先调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//这里看到ArrayList添加元素的实质就相当于为数组赋值
elementData[size++] = e;
return true;
}

2、由add方法引出ensureCapacityInternal方法

//得到最小容量
private void ensureCapacityInternal(int minCapacity) {
if(elementData == DEFAULT_EMPTY_ELEMENTDATA) {
//当数组为空时,获取默认容量和传入参数的较大值
minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

由此可知,当要add第一个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity为10

3、再引出ensureExplicitCapacity方法

如果调用ensureCapacityInternal()方法就一定会执行这个方法:

//判断是否需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;

if(minCapacity - elementData.length > 0){
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}
}

走到这里,可以思考一下:

  • 当要执行add方法进行第一个元素的插入时,elementData.length为0(因为是一个空list),

    接下来执行ensureCapacityInternal方法,使得minCapacity此时为10,接下来执行ensureExplicitCapacity方法,那么minCapacity - elementData.length > 0成立,

    所以会进入grow(minCapacity)扩容方法;

  • 当add插入第二个元素时,minCapacity为2,此时elementData.length(容量)在添加第一个元素之后扩容为10,那么minCapacity - elementData.length > 0不成立,因此不会进入grow扩容方法;

  • 直到添加第11个元素时,minCapacity为11比当前的容量10要大,进入grow方法进行扩容;

4、grow扩容方法


* 要分配的最大数组的大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


* ArrayList扩容的核心方法:grow方法
*/
private void grow(int minCapacity) {
// oldCapacity为旧容量(当前数组的长度),newCapacity为新长度
int oldCapacity = elementData.length;
//将oldCapacity右移一位,其效果相当于oldCapacity / 2,
//位运算远快于整除运算,接下来的结果就是将新容量更新为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,如果还是小于最小需要容量,那么就把最小需要容量
//当作数组的新容量,
if(newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//最后判断新容量大于MAX_ARRAY_VALUE,执行`hugeCapacity()`方法来比较
//minCapacity和Max_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量
//为`Integer.MAX_VALUE`,否则新容量新容量大小则为MAX_ARRAY_VALUE即`Integer.MAX_VALUE`
if(newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData,newCapacity);
}

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!

4.1、grow方法的分析

  • 当add方法插入第一个元素时,oldCapacity为0,经过第一个if判断成立后,newCapacity = minCapacity为10,但是第二个if判断不成立,即newCapacity不会比MAX_ARRAYS_SIZE大,则不会进入 hugeCapacity 方法。数组容量为10,add方法中 return true,size增为1。
  • 当add第十一个元素时进入grow方法时,newCapacity为15,比minCapacity(11)大,第一个if判断不成立,新容量没有大于数组的最大size,也不会进入hugeCapacity方法,数组容量扩容,仅此而已,add方法之后,return true,size增为11;
  • 以此类推。。。

5、hugeCapacity()方法

从上面 grow() 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//对minCapacity和MAX_ARRAY_SIZE进行比较
//若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
//若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}

三、System.arraycopy()Arrays.copyOf()方法

ArrayList中大量调用了这两个方法,扩容操作以及add(int index,E element)toArray等方法中都用到了该方法

3.1、System.arraycopy()方法


* 在此列表中的指定位置插入指定的元素。
*先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal *方法保证capacity足够大;
*再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
*/
public void add(int index,E element) {
rangeCheckForAdd(index);

ensureCapacityInternal(size+1);
//arraycopy()方法实现数组自己复制自己
//参数理解:elementData:源数组;index:源数组起始位置,elementData:目标数组,index+1,目标数组的起始位置,size - index:要复制的元素数量
System.arraycopy(elementData,index,elementData,index+1,size - index);
elementData[index] = element;
size++;
}

Arrays.copyOf()方法


以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
*/
public Object[] toArray() {
//elementData:要复制的数组;size:要复制的长度
return Arrays.copyOf(elementData, size);
}

使用 Arrays.copyOf()方法主要是为了给原有数组扩容

3.3、两者之间的区别和联系

联系

copyOf()内部实际调用了 System.arraycopy() 方法;

区别

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 ;

copyOf() 是系统自动在内部新建一个数组,并返回该数组。