ArrayList源码分析

986 阅读4分钟

ArrayList分析

    private static final long serialVersionUID = 8683452581122892189L;

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

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

ArrayList里常用6个属性:

四个常量属性:

serialVersionUID:是用于在序列化和反序列化过程中进行核验的一个版本号。

DEFAULT_CAPACITY:默认的初始化容量10

EMPTY_ELEMENTDATA:空实例时的数组实例

DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认大小的空实例时的数组实例

前两个解释的比较抽象

elementData:Object数组,集合真正用来存数据的容器。

size:集合大小

关于serialVersionUID可以看一下这篇博客。

serialVersionUID 是干什么的? - 知乎 (zhihu.com)

ArrayList的三个构造器(构造方法):
public ArrayList() //空参
public ArrayList(int initialCapacity) //带有初始化容量
public ArrayList(Collection<? extends E> c)//以一个集合来初始化

因为ArrayList集合底层就是采用elementData[]数组来存储数据,在创建集合时:

1、使用空参构造器时。

只是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组赋值给了elementData数组来完成集合的初始化,因为DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前创建好的,所以在创建集合时的操作只是一个赋值操作,简化了创建数组的时间。

transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2、使用有参(initialCapacity)构造方法时。不废话,先看源码:

image-20230521112431703.png

他会首先判断传进来的initialCapacity是否是大于0的:

如果不大于0他执行的操作和使用空参构造器执行的操作类似,只是这次赋值给elementData数组的是EMPTY_ELEMENTDATA数组,此次空数组赋值竟然和上次空参不一样,这是为什么呢?为什么要创建出两个空数组呢?暂且按下不表

如果大于0,他会新创建一个initialCapacity大小的数组赋值给elementData。至于传进来的是一个小于0的数,当然结果就是死路一条。

3、构造方法参数是集合时。

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

他会首先将传进来的集合转化为一个Object数组,然后判断该数组是否为空。

如果为空,他仍会执行给elementData数组赋值EMPTY_ELEMENTDATA数组的操作,到这有没有发现奇妙的一点?无论集合第一次创建时采用的哪一个构造方法,如果传进来的是一个“空内容”,他都会使用底层已经创建好的数组来完成初始化

如果不为空,判断他们两个是否是同一个集合。如果是,将a数组直接赋值给elementData。如果不是,将a数组复制到elementData

问题:

为什么ArrayList源码里要定义两个空数组呢?直接定义一个岂不是更节省空间?

可以看一下源码中对DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的注释:

We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.

大致意思就是:是为了在第一次添加元素时判断去给数组inflate(扩容)多少。 这就涉及到了集合在第一次添加元素时的操作和集合的扩容。

以下时第一次添加时的源码。

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

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);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

在第一次添加元素时会先调用ensureCapacityInternal方法,同时将添加一个元素后的集合大小size传参过去,在ensureCapacityInternal方法中调用ensureExplicitCapacity()方法,在执行此方法之前,会先调用calculateCapacity方法。如果此时elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA相等,也就是采用的空参构造器的初始化的第一次添加,则返回默认容量和当前容量的较大者,当然第一次添加肯定是返回默认容量(DEFAULT_CAPACITY)。最后进入ensureExplicitCapacity方法,根据minCapacity - elementData.length > 0 判断容量是否足够,然而判断是否要执行扩容方法grow,;

走到这就会发现,ArrayList的初始化容量不会在创建集合时进行,而会在第一次添加元素时进行。而且只有采用的是空参构造方法时,才会在第一次添加元素时将最小容量设置为minCapacity = DEFAULT_CAPACITY = 10。

这还不是最终的扩容,最终的扩容在grow方法中:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);//让newCapacity等于原来容量的1.5倍
    if (newCapacity - minCapacity < 0)                 //如果newCapacity比minCapacity小
        newCapacity = minCapacity;                     //让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);
}

建议

因为ArrayList每扩容一次都要将原数组数据复制到新数组,所以建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略。