「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 表示要拷貝的元素數量(要從原始數組中拷貝多少個)。
相關文章
相關標籤/搜索