ArrayList 源码浅析

127 阅读5分钟

1、ArrayList 与 Array 的关系

Array: 数组

ArrayList: 数组列表

从名字就可以看出,ArrayList 是基于Array基础上实现的列表。为啥有了Array 还需要ArrayList呢?

主要原因是:Java 语言中的数组是用来存储 固定大小 的同类型元素,然而我们在平时的使用中大部分情况下都是需要变更大小的,所以 ArrayList 出现就理所当然了。ArrayList 与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

接下来我们就通过源码来看看 ArrayList 是如何实现的(基于JDK1.8)。

2、ArrayList 源码

2.1、字段


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /**
    * 序列化版本标识,序列化和反序列化时使用
    */
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认容量 10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于ArrayList空实例的共享空数组实例
     */
    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. 
    * 用于默认大小空实例的共享空数组实例。我们将this(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    * 和EMPTY_ELEMENTDATA区别开来,以便在添加第一个元素时知道要膨胀多少。 
    */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
      * 存储 数据的数组
      */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 的大小 和 elementData 容量不同,size 记录的是数组里的元素
     * 在 add,addAll, remove, romoveAll等方法的时候会修改size的值
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

问题一:EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 有什么区别, 在什么时候使用呢?

答:EMPTY_ELEMENTDATA是为了优化创建ArrayList空实例时产生不必要的空数组,使得所有ArrayList空实例都指向同一个空数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了确保无参构成函数创建的实例在添加第一个元素时,最小的容量是默认大小10

2.2、构造方法

/**
 * 构造方法1
 * 指定大小的构造方法
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

/**
 * 构造方法2
 * 什么都不指定的构造方法 , 默认使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 构造方法3
 * 传入集合的构造方法
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
         // Arrays.copyOf 底层用的 System.arraycopy
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

构造方法1 和 构造方法3 都是有参数的构造方法,如果容量为0 使用的是 EMPTY_ELEMENTDATA

而构造方法2 是无参数构造方法,使用的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

2.3、add 方法

/**
 * 新增方法
 *
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // size在这里有变动
    elementData[size++] = e;
    return true;
}

/**
* 确保容量
*/
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}


/**
* 计算容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
   // 当第一次调用add(E e)方法的时候,判断是不是无参构造函数创建的对象,如果是, 
   // 将DEFAULT_CAPACITY即10作为ArrayList的容量,此时minCapacity = 1
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

/**
* 确保容量-扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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


针对 构造方法2 -> DEFAULTCAPACITY_EMPTY_ELEMENTDATA, 会默认使用 DEFAULT_CAPACITY = 10 容量为10, 这样做有两个好处:

1、将默认容量10的分配放到了add的时候,而不是一开始创建的时候。原因是Java应用程序中创建的时候会创建很多空数组对象,如果最开始默认是10的话会占用很多内存。在JDK1.8之前都是默认10,在JDK1.8版本才改的 www.codenong.com/34250207/

2、默认容量10 的话可以防止数组频繁扩容操作

接下来我们看下扩容逻辑的代码

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 位运算符,向右移一位相当于除以2 ,  新容量为旧容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 由于Array不能扩容,只能复制到一个新的数组里
    elementData = Arrays.copyOf(elementData, newCapacity);
}

可见扩容操作是通过将旧的数组数据复制到容量更大的新数组实现的。

3、为何默认容量选择10

答:没什么特别就是觉得 10刚刚好,不大也不小 www.codenong.com/34250207/

2.4 remove 方法

/**
 * 根据下标删除数据
 */
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

/**
 * 删除特定元素 (只会删除第一次出现的)
 */
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                // 删除一次之后就return 
                return true;
            }
    }
    return false;
}


/**
 * 下标范围检查
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


/*
 * 删除元素
 * System.arraycopy(源数组,源数组起始位置,目标数组,目标数组起始位置,复制的长度)
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0) 
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; 
}


image.png

注意:remove的两个方法都是一个参数,当类型变量为Integer的ArrayList调用remove时特别注意下入参的类型

ArrayList<Integer> sites = new ArrayList<>();
sites.add(1);
sites.add(2);
sites.add(4);
sites.add(5);


sites.remove(3);
sites.remove(4); // java.lang.IndexOutOfBoundsException: Index: 4, Size: 4
sites.remove((Integer)4);

总结

1、ArrayList 与 Array 的主要区别在于ArrayList支持自动扩充长度
2、ArrayList 无参数初始化默认是空数组但是会在第一次add的时候将容量扩充为默认容量10
3、扩容策略是每次增加数组的1.5倍大小,采用的是数组复制的方式 System.arraycopy