1. 添加元素:add(E e)
ArrayList 的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;
}
从上述代码可以看出:
- 在添加元素之前需要确保容量充足;
- 假设可能会发生上溢,即
size = Integer.MAX_VALUE
; - 此时传递给
ensureCapacityInternal
的参数为minCapacity = -Integer.MAX_VALUE
(上溢);
2. 确保容量充足:ensureCapacityInternal(int minCapacity)
ArrayList 的ensureCapacityInternal(int minCapacity)
方法源码如下:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
它首先会调用 calculateCapacity
方法来计算所需的容量:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
从上述代码可以看出:
- 这个方法是为无参构造函数创建的 ArrayList 在插入第一个元素时将所需容量设置为
DEFAULT_CAPACITY = 10
; - 这里不会对
minCapacity
进行任何检查,也就是说minCapacity = -Integer.MAX_VALUE
;
在计算出所需容量之后,需要调用 ensureExplicitCapacity
方法来确保容量充足:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 在这里又会发生下溢
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
从上述代码可以看出:
- 这里的条件是
minCapacity - elementData.length > 0
; elementData
是一个 Object 数组,它的大小不会是负数,因此elementData.length >= 0
;- 由于发生了上溢,因此
minCapacity = -Integer.MAX_VALUE
; - 通常情况下
elementData.length
是不会为 0 的(这种情况仅仅会在往空的 ArrayList 中一次性插入Integer.MAX_VALUE + 1
个元素时出现,不太常见),因此我们假设elementData.length > 0
; - 在计算
minCapacity - elementData.length
时,很不幸又发生了下溢,因此最终minCapacity - elementData.length
计算出的是一个正数,按照我们设想的minCapacity = Integer.MAX_VALUE - (elementData.length - 1)
;
现在它会进入 grow
方法来进行扩容。
需要注意的是,它仍然会将 minCapacity = -Integer.MAX_VALUE
传递给 grow
方法。
3. 进行扩容:grow(int minCapacity)
ArrayList 的grow(int minCapacity)
方法源码如下:
/**
* 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);
// 由于 minCapacity = -Integer.MAX_VALUE < 0,不会更新 newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 这里会对 newCapacity 进行检查
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 3.1 条件满足时
newCapacity = hugeCapacity(minCapacity);
// 3.2 条件不满足时
elementData = Arrays.copyOf(elementData, newCapacity);
}
从上述代码中可以看出:
grow
方法不会修改minCapacity
的值;- 由于
minCapacity = -Integer.MAX_VALUE < 0
,不会更新newCapacity
,因此newCapacity
还是原来容量的 1.5 倍; - 如果
newCapacity
超过MAX_ARRAY_SIZE
那么它会调用hugeCapacity
方法,此时分为两种情况,我们等会再说;
3.1 条件满足,即 newCapacity - MAX_ARRAY_SIZE > 0
成立
在条件满足时,它会去执行 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 = -Integer.MAX_VALUE < 0
,此时会抛出 OOM 错误;
3.2 条件不满足时,即 newCapacity - MAX_ARRAY_SIZE > 0
不成立
在条件不满足时,它会执行 Arrays.copyOf
将 Object 数组扩容为原来的 1.5 倍,然后继续返回。
此时会继续执行 add(E e)
方法中剩余的部分:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// 从这里开始执行
elementData[size++] = e;
return true;
}
由于 size + 1
就是 minCapacity
,因此这里会抛出 IndexOutOfBoundsException
异常;
4. 空数组一次性添加 Integer.MAX_VALUE+1
个元素
现在来看看一次性添加多个元素的场景,主要区别在于:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 一次添加大量元素
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
从上述代码中可以看出:
- 一次性添加
Integer.MAX_VALUE+1
个元素时,minCapacity = -Integer.MAX_VALUE
; - 由于
elementData
是一个空数组,因此这里不会发生下溢,也就是根本不会执行grow
方法; - 于是就跟 3.2 节一样,会抛出
IndexOutOfBoundsException
异常; - 需要注意的是,这里一次性添加大量元素使用的是
addAll(Collection<? extends E> c)
方法,它实际上是在System.arraycopy(a, 0, elementData, size, numNew);
中抛出这个异常的,这里就不放上去了;
5. 总结
总的来说,当向 ArrayList 中添加过多元素时会出现以下三种情况:
- 当
newCapacity > MAX_ARRAY_SIZE
时会抛出OOM
错误; - 当
newCapacity > MAX_ARRAY_SIZE
不成立时会抛出IndexOutOfBoundsException
错误; - 当一次性插入大量元素时可能会抛出
IndexOutOfBoundsException
错误;
小白一名,欢迎各位大佬执教~