ArrayList 添加过多元素时会出现那种错误?OOM?IndexOutOfBoundsException?

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

从上述代码可以看出:

  1. 在添加元素之前需要确保容量充足;
  2. 假设可能会发生上溢,即 size = Integer.MAX_VALUE
  3. 此时传递给 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;
    }

从上述代码可以看出:

  1. 这个方法是为无参构造函数创建的 ArrayList 在插入第一个元素时将所需容量设置为 DEFAULT_CAPACITY = 10
  2. 这里不会对 minCapacity 进行任何检查,也就是说 minCapacity = -Integer.MAX_VALUE

在计算出所需容量之后,需要调用 ensureExplicitCapacity 方法来确保容量充足:

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

        // overflow-conscious code
        // 在这里又会发生下溢
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

从上述代码可以看出:

  1. 这里的条件是 minCapacity - elementData.length > 0;
  2. elementData 是一个 Object 数组,它的大小不会是负数,因此 elementData.length >= 0
  3. 由于发生了上溢,因此 minCapacity = -Integer.MAX_VALUE
  4. 通常情况下 elementData.length 是不会为 0 的(这种情况仅仅会在往空的 ArrayList 中一次性插入 Integer.MAX_VALUE + 1 个元素时出现,不太常见),因此我们假设 elementData.length > 0
  5. 在计算 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);
    }

从上述代码中可以看出:

  1. grow 方法不会修改 minCapacity 的值;
  2. 由于 minCapacity = -Integer.MAX_VALUE < 0,不会更新 newCapacity ,因此 newCapacity 还是原来容量的 1.5 倍;
  3. 如果 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);
    }

从上述代码中可以看出:

  1. 一次性添加 Integer.MAX_VALUE+1 个元素时,minCapacity = -Integer.MAX_VALUE
  2. 由于 elementData 是一个空数组,因此这里不会发生下溢,也就是根本不会执行 grow 方法;
  3. 于是就跟 3.2 节一样,会抛出 IndexOutOfBoundsException 异常;
  4. 需要注意的是,这里一次性添加大量元素使用的是 addAll(Collection<? extends E> c) 方法,它实际上是在 System.arraycopy(a, 0, elementData, size, numNew); 中抛出这个异常的,这里就不放上去了;

5. 总结

总的来说,当向 ArrayList 中添加过多元素时会出现以下三种情况:

  1. newCapacity > MAX_ARRAY_SIZE 时会抛出 OOM 错误;
  2. newCapacity > MAX_ARRAY_SIZE 不成立时会抛出 IndexOutOfBoundsException 错误;
  3. 当一次性插入大量元素时可能会抛出 IndexOutOfBoundsException 错误;

小白一名,欢迎各位大佬执教~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值