ArrayList 扩容机制分析

621 阅读4分钟

ArrayList 初始容量

ArrayList 有多个不同的构造方法,不同的构造方法的初始容量是不同的。介绍之前先看下 ArrayList 都有哪些变量

// 默认初始化容量=10
private static final int DEFAULT_CAPACITY = 10;
// 空数组,当初始化容量为0时返回该数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空数组,用于跟 EMPTY_ELEMENTDATA 区分开来,当使用默认构造方法创建的时候返回该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素存放的数组
transient Object[] elementData;
// 元素个数(即list长度)
private int size;
// 记录数组被修改的次数
protected transient int modCount = 0;
// 数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
  1. 无参构造方法,使用默认容量(默认容量为 10),并且设置 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. 传入初始容量(initialCapacity),判断传入容量的值。值 > 0,则 new 一个长度为 initialCapacity 的 Object 数组;值 < 0,直接设置 elementData = EMPTY_ELEMENTDATA

  3. 传入一个 Collection,如果 Collection 的长度为 0,同样的,设置 elementData = EMPTY_ELEMENTDATA;否则,调用 CollectiontoArray 方法创建一个数组,并复制给 elementData

/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/

public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

/**
* Constructs an empty list with an initial capacity of ten.
*/

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/

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;
}
}

ArrayList 相关定义

ArrayList 有两个概念,一个是 capacity,它表示“容量”,表示的是 elementData 的长度。另一个是 size,表示的是数组中存放元素的个数,这个很好理解,我们经常调用 ArrayListsize() 方法返回的就是这个 size

ArrayList 如何实现扩容的?

以无参构造方法为例,初始容量为 10,当调用 add(E e) 方法往 ArrayList 中插入数据的时候,才真正分配容量,即调用 Add(E e) 方法添加第一个元素时,数组扩容为 10

add(E e) 方法为入口,分析源码

/**
* 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;
}

每次调用 add(E e) 方法,都会调用 ensureCapacityInternal 方法,调用完 ensureCapacityInternal 方法后,将元素添加到 elementData 数组的尾部,进入到 ensureCapacityInternal 方法

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

add 第一个元素的时候,minCapacity 为 1 ,因为是添加第一个元素,此时 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 结果为true,通过 Math.max 方法计算出 minCapacity 为 10

接下来看 ensureExplicitCapacity 方法,方法判断 minCapacity 是否大于数组长度,大于的话,表示数组需要扩容,不扩容的话直接往数组添加元素会导致数组越界异常,掉调用 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;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}

默认情况下,新建容量(newCapacity)会是原来容量(oldCapacity)的 1.5 倍(这里使用了 >> 位运算符提高效率),一般情况下,如果扩容 1.5 倍后就大于最小容量(minCapacity),则使用这个 1.5 倍的容量,如果小于最小容量,就返回最小容量的值

当新建容量(newCapacity)大于最大数组长度(MAX_ARRAY_SIZE)的时候,调用 hugeCapacity 方法

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

该方法很简单,当 minCapacity < 0 的时候,抛出内存溢出异常,否则返回最大数组长度

最后,调用 Arrays.copyOf 方法新建一个原数组的拷贝,并修改原数组指向新创建的数组。原数组会被垃圾回收器回收

扩容总结

一般情况下,ArrayList 在调用 add 方法的时候,如果 size 长度不够,则会进行扩容操作,将数组长度扩容到原来的 1.5 倍。如果频繁对 ArrayList 新增元素,则会频繁执行扩容操作影响性能

为避免频繁扩容造成性能下降。当知道将要存入数组的元素数量的时候,可以调用 ArrayList(int initialCapacity) 构造方法显示指定 ArrayList 的初始容量;或者可以通过手动调用 ensureExplicitCapacity(int minCapacity) 方法指定扩容长度,降低默认扩容频率

ArrayList 有缩容吗?

ArrayList 没有缩容。无论是 remove 方法还是 clear 方法,它们都不会改变现有数组 elementData 的长度。但是它们都会把相应位置的元素设置为 null,以便垃圾收集器回收掉不使用的元素,节省内存

扫码关注我 一起学习,一起进步
微信搜索"簧笑语"公众号
微信搜索"簧笑语"公众号