「OpenJdk-11 源码-系列」 ArrayList

ArrayList 定义

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	
	private static final int DEFAULT_CAPACITY = 10;
	private static final Object[] EMPTY_ELEMENTDATA = {};
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	transient Object[] elementData;
	private int size;
}
复制代码

首先能够看到ArrayList继承了AbstractList并实现了List,也就是说ArrayList是一个数组队列,拥有了基本的增删改查,遍历的操做。java

同时实现了数组

  • Cloneable接口,便可以被clone
  • Serializable接口,意味着能够被序列化
  • RandomAccess接口,RandomAccess接口是一个空的接口,和Serializable接口同样,也是做为一个标识,便可以快速访问,对于ArrayList来讲,就是能够经过下标来访问元素。而LinkedList就没有实现这*个接口。

DEFAULT_CAPACITY 和 size 的关系

根据官方注解知道,DEFAULT_CAPACITY是数组总空间大小,而size是数组的当前的容量的大小。举个例子来讲,一个能够装1L水的杯子,那么DEFAULT_CAPACITY就是1L,咱们如今往杯子里倒入了0.5L水,那么size就是0.5L。固然在这,DEFAULT_CAPACITY的默认值是10,当size>10的时候,会进行扩容。安全

为撒 elementData 须要 transient 进行修饰

刚才咱们讲到ArrayList内部其实就是用了一个Object[]来进行维护数据,那既然咱们已经实现了Serializable接口,那为撒还要用transient来修饰elementData呢?来看看序列化/反序列化的代码dom

private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // like clone(), allocate array based upon size not capacity
            SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
            Object[] elements = new Object[size];

            // Read in all elements in the proper order.
            for (int i = 0; i < size; i++) {
                elements[i] = s.readObject();
            }

            elementData = elements;
        } else if (size == 0) {
            elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new java.io.InvalidObjectException("Invalid size: " + size);
        }
    }

	private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioral compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
复制代码

ArrayList在序列化的时候会调用writeObject方法,将数组的sizeelementData写入ObjectOutputStream函数

在反序列化时调用readObject,从ObjectInputStream获取sizeelementData,再恢复到elementData.工具

这样就能够很好的节省空间和时间。由于elementData整个的大小是CAPACITY,通常状况下都会预留一些容量,咱们真正须要序列化/反序列化的只是当前存入的数据。post

构造方法

无参构造方法

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

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

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

第一个空参构造函数,会默认将DEFAULTCAPACITY_EMPTY_ELEMENTDATA的引用传入elementData。在第一次添加元素的时候会扩容一个容量为10的数组。this

第二个指定初始化的 capacity来建立 elementData, 通常推荐使用这种方式来建立ArrayList,减小扩容带来的内存开销。spa

第三个则是传入一个Collection来建立elementData线程

添加

add(E element)

添加方法有三个重载方法,最终都会调用下面这个add方法

private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
    private Object[] grow() {
        return grow(size + 1);
    }
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }
	private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次扩容1.5倍
        //若是扩容后的capcity的大小等于或小于mincapacity,且若是是使用空参构造器初始化的,那么就返回Math.max(DEFAULT_CAPACITY, minCapacity),不然返回 minCapacity(if minCapacity > 0)
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 使用空参构造函数elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }	
复制代码

在第一个方法中,e是指当前add的元素,s指当前arrList的size。能够发现,当size的大小和elementData的长度相等时,才会进行扩容,即调用grow,从newCapacity方法中能够发现,每次扩容会是当前size(即 elementData.length)的1.5倍。

add(int index, E element)

public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }
复制代码

在指定下标条件元素的步骤

  1. 检查index,若是index小于0,或者大于size,那么会抛出IndexOutOfBoundsException异常
  2. modCount++,后续会介绍用处
  3. 判断当前arrayListsize和当前elementData的长度是否相等,若是至关那么先进行扩容
  4. System.arraycopy进行复制,把index这个下标空出来
  5. 赋值

删除

remove(int index)

public E remove(int index) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }
    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }
复制代码

对于remove(int index)源码就比较简单了,惟一须要注意的就是在fastRemove中的判断,若是须要删除的是数组的最后一个元素,那么直接将最后一个元素设置为null,不然进行arraycopy,将index的值覆盖掉。

其它

Fail-Fast 机制

前面咱们常常看到modCount++的操做,那么为何要加这个呢?其实当每次对数组进行操做(修改)的时候,都会进行modCount++,这样作是为了记录修改次数。

咱们知道 ArrayList 不是线程安全的,所以若是在使用迭代器的过程当中若是有其余线程修改(新增/删除)了arrayList的数据(或当前线程在遍历的过程当中对数据进行修改),那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

在每次进行遍历的时候,会先将modCount赋值给expectedCount,在迭代过程当中,进行判断,若是它们不相等,则说明arrayList的数据已经被修改,抛出异常。

Arrays.copyOf 方法和 System.arraycopy 方法的区别?

Arrays.copyOf(T[], int length) 方法是 Arrays 工具类中用来进行任意类型数组赋值,并使数组具备指定长度的方法,ArrayList 中用这个方法来实现 elementData 数组的元素移动。但实际上 Arrays.copyOf 方法最终调用的是 System.arraycopy(U[], int srcPos, T[], desPos, int length) 方法,这个方法是一个native方法

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
复制代码
  • src 表示的是原始数组(源数组)
  • dest 表示的是存放拷贝值的数组(目标数组)
  • srcPos 是指原始数组中的起始位置(从原始数组的哪一个位置开始拷贝)
  • desPos 是指存放拷贝值的数组拷贝起始位置(从目标数组的哪一个位置插入这些拷贝的值)
  • length 表示要拷贝的元素数量(要从原始数组中拷贝多少个)。
相关文章
相关标签/搜索