1. ArrayList和LinkedList總結

ArrayList

類圖

image

總結

  1. ArrayList的默認容量是10

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
  2. 不同於VectorArrayList是線程不安全的,可以使用併發容器CopyOnWirteArrayList代替

  3. ArrayList底層是使用對象數組實現的,繼承了AbstractList,實現了SerializableCloneableRadndomAccess接口

    • transient Object[] elementData; // non-private to simplify nested class access
    • 使用writeObject進行序列化
    • 支持隨機訪問
    • clone()方法實現的是淺複製
  4. writeObject()方法中是先序列化當前元素的個數,再序列化每個元素。因此在readObject()方法中需要先從流中反序列化元素個數,在反序列化每個元素。在這個序列化過程中,是不允許插入、刪除元素和擴容、縮小容量等操作,否則拋出ConcurrentModificationException異常

    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();
        }
    }
  5. ArrayList容量不足時,會進行容量的擴充,新容量是舊容量的1.5倍:int newCapacity = oldCapacity + (oldCapacity >> 1);

    但是,如果新容量不足最小的容量(如默認容量10),則以最小容量爲準

  6. 如果使用默認構造函數創建ArrayList,則此時的elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一個長度爲0的空數組,也就是說,在第一次add()方式時,會先進行擴容操作。這是一種懶惰申請數組空間的方式,防止ArrayList創建以後,卻不使用,從而浪費空間

  7. trimToSize()方法正好相反,是將數組長度消減到實際元素個數。如果此時size == 0則直接將DEFAULTCAPACITY_EMPTY_ELEMENTDATA賦值給elementData,從而避免使用Arrays.copyOf()來複制數組減少消耗

  8. 需要注意clear()方法,僅僅是將數組中的元素全部置爲null,然後修改size = 0,但是並不修改數組的長度

  9. 可以使用Itr/ListItr/get()方法來遍歷數組,僅僅是隨機訪問的話,get()方法更爲高效,因爲get()方法是直接通過下標去訪問元素,而迭代器需要做更多的操作

  10. 在使用ArrayList時,如果提前預知了元素的個數,則最好在構建時生成好預計容量的ArrayList,否則在動態增長過程中,數組的會進行多次複製元素的操作,這是十分影響效率的

  11. ArrayList是有長度限制的,理論最長爲Interger.MAX_VALUE

LinkedList

類圖

image

總結

  1. LinkedList底層使用雙向鏈表實現,所以更適用於順序訪問而不是隨機訪問

  2. LiknedList由於使用鏈表實現,理論上是沒有長度限制的,但是size則可能會出現溢出的情況

  3. LinkedList繼承至AbstractSequentialList,而不是直接繼承AbstractList

    • AbstractSequentialList提供了對List接口的基本實現
      • public E get(int index)
      • public E set(int index, E element)
      • public void add(int index, E element)
      • public E remove(int index)
      • public boolean addAll(int index, Collection<? extends E> c)
      • public Iterator<E> iterator()
    • AbstractSequentialList實現的方法均是(除了iterator())圍繞着抽象方法public abstract ListIterator<E> listIterator(int index);來進行的,所以說在這個基本實現中,全部是依靠ListIterator來對底層鏈表進行操作
    • 注意,如果要實現隨機訪問數組的話最好是直接繼承AbstractList,而不是去繼承AbstractSequentialList
  4. LinkedList實現了List接口,最基本的可以作爲列表使用,同時實現了Deque接口,也就是說可以作爲雙端隊列使用,再加上Deque接口繼承自Queue接口,那麼也可以作爲隊列使用

  5. 基於LinkedList的實現,也可以被用做(鏈)棧使用

    • addFirst()push()
    • removeFirst()pop()
    • getFirst()peek()
  6. LinkedList同時也提供了隊列的基本操作,需要注意的是,LinkedList實現的是無界隊列

    • public boolean add(E e)
    • public boolean offer(E e)
    • public E remove()
    • public E poll()
    • public E element()
    • public E peek()
  7. LinkedList同樣是線程不安全的,可以使用Collections.synchronizedList()來獲取線程安全的LinkedList。如果只是使用隊列,那麼可以使用ConcurrentLinkedQueue來代替

ArrayList和LinkedList對比

相同點

  • 都是List接口的具體實現
  • 兩者都是線程不安全的
  • 均使用了modCount來記錄操作的次數(例如修改數組的結構或內容),在一些方法執行過程中(包括迭代器),不允許其他的操作時,會通過modCount的變化來拋出ConcurrentModificationException異常

不同點

  • 底層實現不同:ArrayList使用的數組,LinkedList使用的是雙向鏈表

  • ArrayList的額外空間消耗是在於通常會在數組的末尾預留一定的空間,LinkedList則是需要保存相關聯結點的指針

  • ArrayList實際上有長度限制,而LinkedList沒有

  • ArrayList更適用於隨機訪問

  • 在插入/刪除元素時,ArrayList相比LinkedList會多付出移動元素以及數組擴容的代價,但是LinkedList同樣也會付出額外空間的代價,同時在指定下標進行元素的操作時,LinkedList會付出更多的代價(進行元素的查找)

  • 在數據量不大的情況,ArrayListLinkedList在操作元素方面效率差不多。但是數據量變大,如果需要在列表的前面操作元素或者伴隨着頻繁/大量的元素刪除,那麼使用LinkedList可能會更好(減少移動元素的情況),但是其他情況下,考慮訪問元素的速度(特別是已知元素上限時),ArrayList可能就是更優的選擇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章