您的位置:首页 > 其它

ArrayList 的扩容机制

2019-07-08 09:56 162 查看

前言

ArrayList 底层是基于数组实现的,不过得益于其扩容机制,它可以看作是一个动态的数组,弥补了数组长度是定长的缺陷。

在往 ArrayList 里添加元素时,如果添加完元素之后,元素的总个数大于当前数组的容量,会执行扩容,这个扩容的过程可以归纳为以下两个步骤:

  • 确定新数组的容量
  • 将老数组内的元素拷贝到新数组中去

确定新数组的容量这一步是本文重点,接下来我们一起来看一下。

 

确定扩容后的数组容量

阅读 ArrayList 的源码,我们会发现不论是执行 add() 还是 addAll() 方法(包括 add() 、addAll() 方法的重载版本也是一样,只不过下面没有贴出来),它在正式添加元素之前,都会先执行

ensureCapacityInternal(int minCapacity)
这个方法来确保容量足够。

传入的参数是添加完元素之后元素的总个数(但要清楚此时还没有进行元素的添加),即扩容后的最小容量:minCapacity,我希望你暂时记住这个参数的意义,后面的源码会不停的根据这个参数做文章。

public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}

public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);  // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

我们接下来去到 ensureCapacityInternal() 方法里面去看一看。

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

ensureCapacityInternal(int minCapacity)
方法里面,它首先会调用
calculateCapacity(Object[] elementData, int minCapacity)
这个方法。

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

calculateCapacity() 这个方法会做一个简单的判断,如果当前的数组是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA(默认容量的数组,这个数组是通过 ArrayList 默认的构造方法创建出来的),会返回默认容量与 minCapacity 中的最大值。ArrayList 的默认容量是 10.

/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;

之后它会调用

ensureExplicitCapacity(int minCapacity)
.

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

这个方法也是做了一个简单的判断,它会检查 minCapacity 是否大于当前数组的大小,如果大于,那么就会调用

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

grow(int minCapacity)
这个方法除了最后一行是将原数组的数据拷贝到新数组中去,其它所有的代码都是在确定新数组的容量。

我们从头到尾来梳理一下。

.

int newCapacity = oldCapacity + (oldCapacity >> 1);
它首先会将容量变为原来的 1.5 倍,然后将这个 newCapacity 与 minCapacity 进行对比。如果 newCapacity 还是比 minCapacity 小,就将 newCapacity 置为 minCapacity。

之后将 newCapacity 与 ArrayList 的数组大小阈值进行对比,这个阈值是 Integer.MAX_VALUE - 8,即 2^31 - 1 - 8.

/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

如果 newCapacity 大于这个阈值,那么就调用

hugeCapacity(int minCapacity)
这个方法,这里请睁大眼睛,它传入的参数是 minCapacity,而不是 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;
}

之后,这个方法第一步做的这个判断,让我百思不得其解,因为就目前 ArrayList 这个实现方式来看,minCapacity 是无论如何都不可能出现小于 0 的情况,所以这里大家可以忽略这段代码。

< 1c6f4 p>最后我们来看一下返回值,这个是 hugeCapacity() 方法的重点。它会判断 minCapacity与 MAX_ARRAY_SIZE 这个阈值的大小,如果 minCapacity 大的话,就直接将 newCapactiy 定为 Integer.MAX_VALUE,否则使用 MAX_ARRAY_SIZE 这个阈值作为新数组的容量,即 newCapactiy 为 Integer.MAX_VALUE - 8.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: