Java容器ArrayList源码浅析

在Java给咱们提供的各类容器类中,最经常使用的就是ArrayList了吧,你可能早就把它用烂了,但它内部细节是怎么实现的,数组怎么动态增加的呢?今天咱们就来看一下ArrayList的源码一探个究竟。html

本文分析的ArrayList源码基于JDK 1.8java

1. ArrayList 的定义

首先打开ArrayList类,看一下这个类的定义:数组

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
复制代码

ArrayList继承自AbstractList,支持泛型。bash

而后实现了这些接口:dom

  • List 说明该类是一个有序的序列集合。
  • RandomAccess 说明该类支持时间复杂度为O(1)随机访问;RandomAccess接口通常只用在实现了List接口的类。
  • Cloneable 说明该类支持被克隆。
  • Serializable 说明该类支持序列化。

ArrayList还间接实现了Iterable和Collection接口。函数

2. ArrayList 的属性

ArrayList 中声明了下面这些属性:ui

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 序列化ID
    private static final long serialVersionUID = 8683452581122892189L;

    // ArrayList的默认初始容量
    private static final int DEFAULT_CAPACITY = 10;

    // 一个空的对象数组,用来初始化内容为空的ArrayList实例
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 数据对象数组,真正的数据都保存在这个数组之中,标了transient关键字说明该对象数组不参与序列化
    transient Object[] elementData; 

    // 当前ArrayList中包含的元素的个数
    private int size;
    
    // 当前ArrayList被修改过的次数
    protected transient int modCount = 0;
    // ...
    
}
复制代码

3. ArrayList 的构造函数

ArrayList分别提供了下面三个构造函数,首先来看下咱们最常使用的无参构造函数:this

public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
复制代码

很简单,调用了下父类的构造函数(点进去发现父类构造函数是个空实现),而后将以前声明的空对象数组EMPTY_ELEMENTDATA赋值给elementData属性。spa

接着看下能够传初始容量的构造函数:code

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
复制代码

也是先调用父类构造函数,而后判断若是传进来的初始化容量initialCapacity小于0则抛出异常,不然建立一个大小为initialCapacity的对象数组并赋值给elementData。

最后还有个能够传集合的构造函数:

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
复制代码

该方法要求传入实现了Collection接口的类的对象,而后调用Collection类中的toArray()方法便可将该集合对象中包含的所有元素转换为对象数组。 接着再初始化size。

按道理来讲这就应该初始化完毕了吧,但是为何后面又紧跟了一句:

if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
复制代码

原来Collection类的toArray()返回的不必定是Object[]类,举个例子:

public static void main(String[] args) {
        Collection c = Arrays.asList("a", "b", "c");
        System.out.println(c.toArray().getClass());
        List<Object> list = new ArrayList<>(c);
        list.set(0,new Object());
    }
复制代码

运行会发现,打印输出的是class [Ljava.lang.String;,所以此时ArrayList内部的elementData即为String类型了,这时候调用最后一句list.set(0,new Object())则会抛出ArrayStoreException异常,由于你不能往一个String数组设置对象。

4. ArrayList 添加数据

4.1 add(E e)

先看下最经常使用的add(E e)方法:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
复制代码

该方法先调用了ensureCapacityInternal()方法,从方法名能够猜想,这个方法应该就是用来对数组进行动态扩容的,跳转到该方法:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
复制代码

该方法的参数minCapacity表示当前要求的最小容量,咱们前面传过来的是size + 1,即最小容量只要比当前元素数量多1就够了。

若是elementData == EMPTY_ELEMENTDATA,则说明该ArrayList是经过无参构造函数构造的,这时候咱们的minCapacity取默认容量(10)的和传进来最小容量中较大的那一个。

接着又将minCapacity传给ensureExplicitCapacity()方法:

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
复制代码

先自增了一下modCount,而后接着的代码就好玩了,为何不直接

if (minCapacity > elementData.length){
        grow(minCapacity);
    }
复制代码

而要作个减法跟0比较呢?而后他这儿还有个注释,说overflow-conscious code又是怎么回事呢?

好接着点进grow()方法:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
复制代码

看到这里,我不由产生了关掉IDE的冲动。

好的,分状况讨论一下。

  • 先假设传进来的minCapacity = 11,对象数组elementData目前大小是10,即oldCapacity = 10,newCapacity在oldCapacity的基础上增长一半,即newCapacity = 15;newCapacity - minCapacity = 4 > 0,第一个条件不经过;newCapacity - MAX_ARRAY_SIZE显然小于0,第二个条件也不经过,因此到下一步newCapacity仍是等于15,而后调用Arrays.copyOf(elementData, newCapacity)建立了一个长度为15的数组副本,将原数组中的数据拷贝到这个数组副本中,最后将数组副本赋值给elementData。看一下Arrays.copyOf()方法的源码就知道了:
public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
    
复制代码
  • 如今咱们假设minCapacity = 16,对象数组elementData的大小仍是10,那么跟刚刚惟一的区别就是newCapacity - minCapacity = -1 < 0,第一个条件经过,即newCapacity = 16,剩下仍是同样。

  • 接下来就好玩了,若是当前elementData的大小已经超级大了,我是说超级超级大,大到了接近Integer.MAX_VALUE。

咱们如今假设elementData的大小为Integer.MAX_VALUE - 100(即2147483547),则minCapacity = Integer.MAX_VALUE - 99(即2147483548),那么此时再计算newCapacity则会溢出,此时newCapacity = -1073741976,变成了负数;此时newCapacity - minCapacity = 1073741772 > 0,不知足第一个条件;newCapacity - MAX_ARRAY_SIZE = 1073741681 > 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;
    }
复制代码

hugeCapacity方法先判断minCapacity是否是溢出了,若是溢出了就抛出OutOfMemoryError,接着若是minCapacity没有溢出可是比MAX_ARRAY_SIZE大,则返回Integer.MAX_VALUE,不然就返回MAX_ARRAY_SIZE。 在咱们举例的这个状况下,hugeCapacity方法返回MAX_ARRAY_SIZE,所以最终的newCapacity就等于MAX_ARRAY_SIZE。

接下来考虑一下,若是咱们把grow方法改写成下面这样会发生什么:

private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity < minCapacity)
            newCapacity = minCapacity;
        if (newCapacity > MAX_ARRAY_SIZE)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
复制代码

仍是套用咱们刚才的数据,oldCapacity = Integer.MAX_VALUE - 100(2147483547),minCapacity = Integer.MAX_VALUE - 99(即2147483548),newCapacity = -1073741976。会发现,修改过得代码跟原代码的运做恰好相反,第一个条件经过,第二个条件则不经过,即不会调用hugeCapacity方法。这样的话ArrayList就没法正确地扩容。

到这里扩容部分的代码就结束了,回来看一下咱们开始的add方法:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
复制代码

正确扩容后,直接将元素e赋值给对象数组elementData下标为size的位置,由于此时size就是添加的元素应该在的下标,而后将size自增。

4.2 add(int index, E element)

接下来看下add(int index, E element)方法,在特定的下标index处插入元素:

public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
        elementData[index] = element;
        size++;
    }
    
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
复制代码

首先调用了rangeCheckForAdd方法检查下标index是否合法,不合法抛出异常IndexOutOfBoundsException。

而后是调用ensureCapacityInternal方法进行扩容,确保ArrayList容量够存放size + 1个元素。

接下来的System.arraycopy(elementData, index, elementData, index + 1,size - index)作的是将从index开始的元素所有都日后移动一位,若是原来的元素是[1,2,4,5],index = 2,element = 3,通过这步会变成[1,2,4,4,5]。

接着就把元素element覆盖到数组中index的位置。即变成了[1,2,3,4,5]。

最后将size自增。

该方法最坏的状况是index = 0,由于这样的话当前ArrayList中所有的元素都得日后移动一位。

4.3 addAll(Collection<? extends E> c)

咱们刚看完了添加单个元素,如今来看下如何添加多个元素,ArrayList提供了addAll(Collection<? extends E> c)方法:

public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
复制代码

该方法的参数也是Collection接口,调用Collection提供的toArray方法将Collection中的所有元素转换为对象数组a;接着获取该数组的大小并赋值给numNew,而后一样是调用ensureCapacityInternal进行扩容。确保ArrayList容量能够存放size + numNew个元素后,直接将对象数组a中的所有元素从末尾拷贝进elementData。最后正确增长size,若是numNew不为0返回true,不然返回false。

这里我不理解的是,为何不先判断集合是否为空,若是为空直接返回false就省去了后面的方法调用,有知道的朋友麻烦告诉我噢!

4.4 addAll(int index, Collection<? extends E> c)

ArrayList还提供了addAll(int index, Collection<? extends E> c)方法,能够从指定位置index开始插入多个元素:

public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
复制代码

一样先经过调用rangeCheckForAdd方法判断index是否合法,index合法后接下来仍是先调Collection的toArray方法转换为对象数组a,而后同样调用ensureCapacityInternal方法。

跟前面的addAll方法不一样的是,咱们这里可能要移动一些元素,所以经过size - index先计算要移动的元素个数,若是numMoved大于0则说明须要移动元素,即将index开始的元素通通日后numNew位;不然numMoved等于0,说明直接从末尾添加,无需移动元素。

接下来将对象数组a中的所有元素从index开始拷贝到elementData中。最后正确增长size。若是numNew不等于0返回true。

5. ArrayList 访问数据

访问数据比较简单,ArrayList提供了get()方法:

public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
    
复制代码

首先调用rangeCheck(int index)方法检查下标是否超出了范围,而后经过调用封装好的elementData(int index)方法获取index对应的元素。

6. ArrayList 查找数据

6.1 indexOf(Object o)

public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
复制代码

indexOf分两种状况,若是查找的对象为null,则经过for循环找到第一个为null的元素的位置并返回了;若是查找的对象不为null,则经过调用equals()方法判断是否同一个元素,找到即返回位置;若是没找到返回-1。

6.2 lastIndexOf(Object o)

public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
复制代码

lastIndexOf()方法跟indexOf()方法惟一的区别就是它的for循环是从后往前的。

6.3 contains(Object o)

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
复制代码

contains方法是经过调用indexOf方法实现的,若是indexOf方法返回的数字大于等于零,说明对象o存在,不然返回-1。

7. ArrayList 删除数据

7.1 remove(int index)

remove(int index)方法用来删除指定下标的元素:

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;
    }
复制代码

remove方法的操做是,把index后面的元素都往前移一位,而后删除最后一个元素。

7.2 remove(Object o)

remove(Object o) 能够用来删除某指定元素

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 true;
                }
        }
        return false;
    }
复制代码

该方法跟indexOf(Object o)很是类似。remove(Object o)方法找到元素所在位置后,调用fastRemove(int index)方法删除元素:

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; // clear to let GC do its work
    }
复制代码

fastRemove(int index)跟remove(int index)方法的区别是,它不检查index,也不返回删除的元素。

8. ArrayList 修改数据

ArrayList提供了set(int index, E element)方法来更新某位置的值,也比较简单:

public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
复制代码

先调用rangeCheck()方法保证index合法,接下来调用elementData()方法获取index位置的元素并保存在局部变量oldValue中,而后将该位置的元素更新为element,最后返回oldValue。

9. 其余

9.1 trimToSize()

trimToSize()方法能够用来将对象数组的大小压缩到跟size同样大:

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }
复制代码

9.2 size()

size()方法返回当前包含的元素大小

public int size() {
        return size;
    }
复制代码

9.3 isEmpty()

判断当前ArrayList内的元素是否为空

public boolean isEmpty() {
        return size == 0;
    }
复制代码

9.4 toArray()

返回包含当前ArrayList所有元素的对象数组

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
复制代码

9.5 clear()

清除当前ArrayList中所有元素

public void clear() {
        modCount++;
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }
复制代码

就把对象数组中每一个元素设为null,而后size重置为0。

9.6 sort(Comparator<? super E> c)

根据传进来的Comparator对ArrayList中的元素进行排序:

public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
复制代码

具体的排序经过调用Arrays中的静态方法sort(T[] a, int fromIndex, int toIndex,Comparator<? super T> c)。

10. 参考

相关文章
相关标签/搜索