**Arraylist扩容方式的深入个人理解**
概述
ArrayList 是 基于Object数组实现的一种动态数组集合。当向 ArrayList 添加元素时,如果当前数组已满,就需要对数组进行扩容。扩容的过程是这样进行的:
创建一个新的数组,即新数组大小为原数组大小的 1.5 倍。
int newCapacity = oldCapacity + (oldCapacity >> 1);
将原数组中的所有元素复制到新数组中并,将将新元素添加到新数组中。并将新数组替换原数组,原数组成为垃圾对象,在垃圾回收时会被回收。
这种扩容方式的时间复杂度为 O(n),其中 n 为数组中的元素数量。因为需要将所有元素复制到新数组中,所以扩容操作的开销比较大。
为了减少扩容操作的次数,ArrayList 通常会采用一种“预留空间”的策略。当 ArrayList 创建时,会为数组预留一定的空间,采用ArrayList的有参构造创建对象,这样在添加元素时,只有当数组大小达到预留空间时才进行扩容操作。
可以看出,当 ArrayList 需要扩容时,它需要分配新的数组并将元素复制到新数组中。这个过程会涉及到数组分配、复制和垃圾回收等操作,因此可能非常昂贵。为了避免频繁的扩容操作,ArrayList 通常会在初始化时为数组预留一些空间。我们可以通过构造函数中的初始容量参数来设置预留空间的大小,以此来控制扩容的频率和代价。
总的来说,ArrayList 的扩容方式虽然时间复杂度比较高,但是它提供了一种高效的动态数组实现方式,适用于需要频繁进行查询读取数据等操作的场景。在实际应用中,我们需要根据具体的场景和性能要求来合理使用 ArrayList 并设置预留空间大小。
例如,如果我们知道 ArrayList 中最多可能会存储 1000 个元素,我们可以在初始化时设置 ArrayList 的容量为 1000,这样就可以避免多次扩容操作,提高性能。
源码角度分析扩容
从源码的角度来看,ArrayList 扩容的实现主要依赖于 ensureCapacityInternal 和 grow 两个方法。让我们来看一下这两个方法的实现细节。
ensureCapacityInternal(int minCapacity)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
该方法首先判断 elementData 是否为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA常量对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
如果采用的是无参构造方法创建的ArrayList对象,第一次添加元素时,会将minCapacity设置为DEFAULT_CAPACITY,
// DEFAULT_CAPACITY常量对象
private static final int DEFAULT_CAPACITY = 10;
常量DEFAULT_CAPACITY=10;则将 minCapacity 设置为 DEFAULT_CAPACITY 和 minCapacity 之间的最大值,即初始化时的默认容量。
// 首先判断elementData对象是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则将 minCapacity 设置为 DEFAULT_CAPACITY 和 minCapacity 之间的最大值,即初始化时的默认容量。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
然后调用 ensureExplicitCapacity 方法,该方法会进行实际的扩容操作。
grow(int minCapacity)
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
该方法首先计算新的容量 newCapacity,该值为原容量的 1.5 倍。然后根据 minCapacity 和 MAX_ARRAY_SIZE 的值,对 newCapacity 进行调整。最后使用 Arrays.copyOf 方法将原数组复制到新数组中,完成扩容操作。
需要注意的是,ArrayList 的最大容量为 Integer.MAX_VALUE,也就是说,当需要扩容的容量超过该值时,会抛出 OutOfMemoryError 异常。
小结
综上所述,ArrayList 扩容的实现主要是通过 ensureCapacityInternal 和 grow 方法来实现的。在 ensureCapacityInternal 方法中,会先判断 elementData 是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,则将 minCapacity 设置为DEFAULT_CAPACITY 和 minCapacity 之间的最大值,即初始化时的默认容量。然后调用 ensureExplicitCapacity 方法,该方法会进行实际的扩容操作。在 grow 方法中,会计算出新的容量 newCapacity,然后根据 minCapacity 和 MAX_ARRAY_SIZE 的值,对 newCapacity 进行调整。最后使用 Arrays.copyOf 方法将原数组复制到新数组中,完成扩容操作。