jdk1.8-Java集合框架之--ArrayList源碼分析

1、說到ArrayList,你們都知道是有序集合,有下標,讀速度快,寫速度慢。可是問一句爲何是有序、爲何有下標、爲何讀速度快、爲何什麼寫速度慢,估計不少人答不上來。那麼接下來咱們就從源碼的角度來分析。

2、咱們先來看一張圖:

經過這張圖,咱們能夠看到,ArrayList類,繼承了一個抽象類AbstractList,實現了四個接口,分別是Cloneable、Serializable、RandomAccess、List這四個接口。Cloneable、Serializable這兩個接口咱們知道是對象克隆和序列化相關的,咱們暫時無論。

  1. 咱們先看一下RandomAccess接口。
public interface RandomAccess {
}
複製代碼

打開源碼一看,這是一個空接口,即標記接口,這個接口的做用就是使ArrayList具有快速隨機訪問的功能。什麼是隨機訪問?就是隨機訪問ArrayList中的任何一個元素。那ArrayList是如何快速隨機訪問任何一個元素的呢,咱們稍後再說明。數組

  1. 咱們再來看下父類AbstractList,從上面的圖1能夠看出,AbstractList是一個抽象類,也一樣實現了List接口,咱們能夠猜測,AbstractList是對List的具體實現類進行的抽象化的提取。因爲篇幅緣由,代碼爲就不貼了。bash

  2. 接下來咱們就看ArrayList類代碼的具體實現,首先咱們看到ArrayList類定義了4個常量,2個實例變量,分別是:框架

//默認容量
    private static final int DEFAULT_CAPACITY = 10;
    //空元素數組
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //默認容量對應的空元素數組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //元素,被transient關鍵字修飾,無需序列化
    transient Object[] elementData; // non-private to simplify nested class access
    //長度
    private int size;
    //最大長度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製代碼
從以上源碼咱們能夠猜測,ArrayList是經過內部維護一個的Object對象數組實現的,且默認的ArrayList容量是10。
複製代碼
  1. 接下來看下ArrayList的構造函數,ArrayList一共定義了3個構造函數,分別是:
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //指定初始化容量,若是指定容量大於0,則設置元素數組爲指定長度的對象數組;
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //若是指定容量等於0,那麼設置元素數組爲空數組;
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //若是指定容易小於0,則拋出異常;
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }

    //無參構造函數,則設置元素數組爲空數組
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        //將集合轉爲數組,並設置爲當前對象的元素數組;
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 若是元素數組長度不爲0,且數組對象不是Object,則將數組元素轉爲Object對象;
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 若是元素數組長度爲空,則替換成常量空數組;
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
複製代碼
  1. 接下來咱們具體分析下ArrayList的幾個經常使用方法,首先是get:
public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    E elementData(int index) {
        return (E) elementData[index];
    }
複製代碼

很明顯,咱們能看得出,get方法純粹就是ArrayList內部維護的元素數組操做,經過數組下標取值,很是簡單。至此,回顧一開始的問題,ArrayList爲何讀取快?由於是數組下標直接讀取操做,複雜度是O(1)。dom

  1. 繼續看無參數add方法:
public boolean add(E e) { 
        //將數組長度+1做爲add操做須要的最小容量傳入方法中
        ensureCapacityInternal(size + 1); 
        elementData[size++] = e;    //將元素添加到數組末端,所以主要邏輯在前一句代碼
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    //根據當前元素數組長度,和add操做須要的最小容量作比較,肯定操做須要的最小容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //該步驟確保元素數組知足操做所需的最小容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 若所需的最小容量大於當前元素數組長度,則對元素數組進行擴容處理
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //擴容操做
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 新容量=舊容量+舊容量/2;
        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);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
複製代碼

以上無參數的add方法源碼,其實帶參數的add方法跟無參數的基本同樣,只是增長了一個指定位置到數組末端元素複製的方法,以下:函數

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++;
    }
複製代碼

能看得出,ArrayList的無參數add方法,須要通過兩個大步驟,第一步是計算容量,第二步是若是擴容了,須要將原來數組內的元素,全量複製到新的數組上。至此,咱們又能回答一開始的問題,ArrayList爲何寫操做慢?由於ArrayList寫操做會對部分甚至所有元素進行移動操做,同理,對於set、remove等寫操做的方法,總體上都是這樣的操做,因此寫操做效率低。ui

第一篇Java集合框架之ArrayList就簡單講到這,下節講LinkedList,有不一樣意見或建議歡迎指出,謝謝。

相關文章
相關標籤/搜索