1. 程式人生 > 實用技巧 >ArrayList PK LinkedList

ArrayList PK LinkedList

​ ArrayList和LinkedList,這兩個集合大家都不陌生.尤其是ArrayList,可以說是日常開發中用的最多的容器了.而且這兩個集合的知識點幾乎可以說面試必問的.

ArrayList

​ ArrayList是List介面的一個實現類,底層是基於陣列實現的儲存結構,資料都是存放到一個數組中.

所以ArrayList的優缺點與陣列大致相同,區別是ArrayList可以動態擴容

優點 缺點
隨機訪問性強 插入和刪除效率低
查詢效率高 可能浪費記憶體
支援動態擴充套件

ArrayList原始碼分析

ArrayList的屬性

	 /**
     * 預設初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空陣列(用於空例項)。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

 	/**
     * 用於預設大小空例項的共享空陣列例項。我們把它從EMPTY_ELEMENTDATA陣列中區分出來,以	 * 	知道在新增第一個元素時容量需要增加多少。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 儲存ArrayList資料的陣列
     * 將不需要序列化的屬性前新增關鍵字transient,序列化物件的時候,這個屬性就不會被序列化。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 所包含的元素個數
     */
    private int size;

ArrayList的構造方法

	 /**
     * 帶初始容量引數的建構函式。(使用者自己指定容量)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //建立initialCapacity大小的陣列
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //建立空陣列
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     *	預設建構函式,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 為0.初始化為10,	  
     *	也就是說初始其實是空陣列 當新增第一個元素的時候陣列容量才變成10
     *	看擴容過程的ensurecapacityinternal()方法!
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList的擴容

	public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)  
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
 
   //得到最小擴容量
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              //若是無參構造時,用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA陣列
              // 獲取預設的容量和傳入引數的較大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
             //否則直接去判斷是否需要擴容
        ensureExplicitCapacity(minCapacity);
    }
 
  //判斷是否需要擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            //呼叫grow方法進行擴容,呼叫此方法代表已經開始擴容了
            grow(minCapacity);
    }
 
    /**
     * 最大陣列大小
     * 陣列作為一個物件,需要一定的記憶體儲存物件頭資訊,物件頭資訊最大佔用記憶體不可超過8位元組。
     */
//https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 
    /**
     * ArrayList擴容的核心方法。
     */
    private void grow(int minCapacity) {
        // oldCapacity為舊容量,newCapacity為新容量
        int oldCapacity = elementData.length;
        //將oldCapacity 右移一位,其效果相當於oldCapacity /2,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麼就把最小需要容量當作陣列的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //再檢查新容量是否超出了ArrayList所定義的最大容量,
        //若超出了,則呼叫hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE,
        //如果minCapacity大於MAX_ARRAY_SIZE,則新容量則為Interger.MAX_VALUE,否則,新容量大小則為 MAX_ARRAY_SIZE。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //比較minCapacity和 MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

序列化

/**
 * 每次序列化的時候呼叫這個方法,先呼叫defaultWriteObject()方法序列化ArrayList
 * 中的非transient元素,elementData這個陣列物件不去序列化它,而是遍歷elementData,
 * 只序列化數組裡面有資料的元素,這樣一來,不僅提高了序列化的效率,還減少了空間的開銷。
 *            
 */
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 behavioural 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();
    }
}

LinkedList

​ LinkedList 是基於雙向連結串列實現的,不需要指定初始容量,連結串列中任何一個儲存單元都可以通過向前或者向後的指標獲取到前面或者後面的儲存單元.所以LinkedList 的優缺點與連結串列基本相同

優點 缺點
插入刪除速度快 不能隨機查詢
內容利用率高 必須從第一個開始遍歷,查詢效率低
大小沒有固定,擴充套件和靈活

LinkedList 原始碼分析

/**
*	內部私有類Node
*/
private static class Node<E> {
    //節點值
    E item;
    //後繼節點
    Node<E> next;
    //前驅節點
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

哪個更佔空間?

​ 一般情況下,LinkedList的佔用空間更大,因為每個節點要維護指向前後地址的兩個節點

​ 如果剛好資料量超過ArrayList預設的臨時值時,ArrayList佔用的空間也是不小的,因為擴容的原因會浪費將近原來陣列一半的容量.