乐趣区

谈谈你所不知道的ArrayList缩容

前言

每当大家谈起 ArrayList 都只会关注它的自动扩容机制,但是大多数人却不会去关注 ArrayList 是否会自动缩容。下面会根据几个问题让大家了解一下 ArrayList 的缩容机制。

PS:缩容指的是 ArrayList 中的 Object 数组 elementData 的大小缩减

ArrayList是否会自动缩容?

既然 ArrayList 的自动扩容一般是发生在 add()addAll()方法中,那么当 ArrayList 调用 remove() 方法时是否会自动缩容呢?

ArrayList#remove()源码分析

remove()方法有两个

  1. 入参为数组下标返回被删除的元素
  2. 入参为数组元素返回数组下标

PS:本文研究的 JDK 源码为 Oracle JDK 1.8

这里对第 1 个方法的源码进行分析

    public E remove(int index) {
        // 校验传入的下标是否大于或等于当前 ArrayList 的长度 size
        // 如果超过直接抛出 IndexOutOfBoundsException
        rangeCheck(index);
        
        // 修改次数自增
        // modCount 是用于快速失败机制的,这里不展开
        modCount++;
        // 获取需要删除的元素的值
        E oldValue = elementData(index);
        
        // 计算数组从需要删除的元素后需要移动的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 使用 System.arraycopy()来进行数组元素左移
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 多余的元素标记为 null 便于 gc 回收,同时 size 自减
        elementData[--size] = null;
        // 返回被删除的元素值
        return oldValue;
    }

PS:System#arraycopy()并不会影响源数组和目标数组的长度。

ArrayList#remove()源码图解

如果觉得源码比较难懂,可以参考下面的 ArrayListremove()方法关键过程图解

从上面可以看出 ArrayList 在执行 remove() 方法时,内置的 Object 数组实际上并没有减少长度,只是通过数组部分元素左移、最后一个元素置为 null 同时 size 自减来实现删除操作。而且入参为数组元素返回数组下标的 remove()removeAll()removeRange()也是类似的做法。

ArrayList#clear()源码分析

除了 remove() 以外,ArrayList还有一个 clear() 方法用来对数组进行清空。那它又是怎么工作的呢?实际上简单到我不敢相信

public void clear() {
        // 修改次数自增
        modCount++;

        // 遍历数组并置为 null 便于 gc 回收
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        // 直接给 size 赋值为 0
        size = 0;
    }

结论

从上面两节源码的分析可以得知,实际上 ArrayList 是不提供自动缩容机制的。其实这样设计也无可厚非,毕竟很多时候都无法判断是否需要进行缩容操作。既然没有自动缩容,那 ArrayList 可以进行缩容吗?

ArrayList可以进行缩容吗?

当然是 有的 ,那就是trimToSize() 方法。这个方法会将 ArrayList 内置的数组缩容到当前的 size,源码也特别简单,就是给elementData 一个对应 size 长度的数组。源码如下

    public void trimToSize() {
        // 修改次数自增
        modCount++;
        // 判断当前是否需要缩容
        if (size < elementData.length) {
            // 如果 size 为 0,直接给 elementData 赋值内置的空数组
            // 不为 0 则创建一个 size 长度的新数组
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

总结

  1. ArrayListremove 只是通过数组部分元素左移、最后一个元素置为 null 同时 size 自减来实现删除操作。实际上并未对 elementData 进行缩容。
  2. 可以使用 trimToSize() 方法对 elementData 进行缩容。
退出移动版