常見Java集合實現細節——ArrayList和LinkedList

3、ArrayList和LinkedList

       在List集合的實現類中,主要有三個實現類:ArrayList、Vector、LinkedList。其中Vector還有一個Stack子類,這個Stack子類僅在Vector父類的基礎之上增加了五個方法,這五個方法使得Vector擴展成爲Stack。
//stack源代碼
public class Stack<E> extends Vector<E> {
    public Stack() {
    }

    public E push(E item) {
        addElement(item);

        return item;
    }

    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

    public boolean empty() {
        return size() == 0;
    }

    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

}
        從上面的代碼可以看出,Stack本質依然是一個Vector,並且是一個線程安全的類,因此Vector也是一個線程安全的類,只是增加了五個使用synchronized修飾的方法。
       實際上,即使程序中需要這種數據結構,Java也不推薦使用Stack類,而是推薦使用Deque實現類。從JDK1.6開始,Java提供了一個Deque接口,併爲該接口提供了一個ArrayDeque實現類。在無須保證線程安全的情況之下,程序中完全可以使用ArrayDeque來代替Stack類。Deque接口代表雙端隊列的數據結構。雙端隊列已經不再是簡單的隊列,它既具有隊列的性質(FIFO),也具有棧的性質(FILO)。

3、1 Vector與ArrayList區別

       Vector和ArrayList這兩個集合類的本質並沒有太大的卻別,它們都實現了List接口,而且底層都是基於Java數組來存儲集合元素的。
       從序列化機制的角度來看,ArrayList的實現比Vector的實現更安全。除此之外,Vector其實就是ArrayList的線程安全版本,ArrayList和Vector絕大部分的方法實現都是相同的,只是Vector的方法增加了synchronized進行修飾。除此之外,Vector是一個非常古老的集合,從JDK1.0就開始存在了,那時候它已經包含了大量從操作集合元素的方法,但是這些方法都具有方法名冗長、難於記憶的特點。隨着JDK1.2增加了List接口之後,Vector改爲實現了List接口,因此Vector將原有的方法進行了包裝,包裝成了遵循List接口規範的新方法。
       由於Vector包含的方法比ArrayList更多,而且ArrayList的序列化實現比Vector的序列化實現更安全,因此Vector基本上已經被ArrayList所代替。Vector唯一的好處就是線程安全。
提示:即使需要在多線程的環境下使用List集合,而且需要保證List集合的線程安全,則應該考慮將ArrayList包裝成線程安全的集合類。Java提供了一個Collections工具類,通過該工具類的synchronizedList方法即可將一個普通的ArrayList包裝成一個線程安全的ArrayList。

3、2 ArrayList與LinkedList區別

       List代表了一種線性表的數據結構,ArrayList則是一種順序存儲的線性表,底層是採用數組來保存每一個集合元素,LinkedList則是一種鏈式存儲的線性表,其本質上就是一個雙向鏈表,但它不僅實現了List接口,還實現了Deque接口。也就是說,LinkedList既可以當成雙向鏈表使用,也可以當成隊列使用,還可以當成棧來使用(Deque代表雙端隊列, 既具有隊列的特徵,也具有棧的特徵)。
       ArrayList底層採用一個數組來保存所有的集合元素,因此在插入元素時要完成下面兩件事情。
  • 保證底層封裝的數組長度大於集合元素的個數。
  • 將插入位置之後的所有數組元素整體向後移動一個位置。
       反過來,當刪除ArrayList集合中指定位置的元素時,程序也要進行整體移動,而且還需要將被刪除索引的地方的數組元素賦爲null。因此,對於ArrayList集合來說,當程序向其中添加、刪除元素時,ArrayList底層都要對數組進行整體的移動,所以性能非常差。但如果程序調用get(int index)方法來取出ArrayList集合中的元素時,性能和數組一樣都非常的快

       LinkedList本質上就是一個雙向鏈表,因此它在添加集合元素時,只要對鏈表進行如圖所示的操作即可添加一個新的節點,因此可以非常方便地在指定節點之前插入新節點。如果只是單純地添加某個節點,那麼LinkedList的性能會非常的好;但如果需要向指定索引處添加節點,LinkedList必須先找到指定索引處的節點——這個搜索過程的系統開銷並不小。
       但是LinkedList取出指定的元素就比較麻煩,必須逐個元素地搜索,直到找到第index個元素爲止,因此對於系統的開銷也比較大。

3、3 ArrayList與LinkedList性能分析及適用場景

       當程序需要以get(int index)方法獲取List集合指定索引處的元素時,ArrayList性能大大地由於LinkedList。因爲ArrayList底層以數組來保存集合元素,所以調用該方法獲取指定索引處的元素時,底層實際上調用elementData[index]來返回元素,因此性能非常好。而LinkedList則必須逐個地搜索。
       當程序調用add(int index , Object obj)方法向List集合中添加元素時,LinkedList性能高於ArrayList。因爲ArrayList必須對底層數組元素進行整體移動。如果添加元素導致集合長度超過了底層數組長度,ArrayList必須創建一個長度爲原來長度1.5倍的數組,再由垃圾回收機制回收原有數組,因此係統開銷比較大。對於LinkedList而言,它的主要開銷集中在entry(int index)方法,該方法必須逐個地搜索,直到找到index處的元素,然後在該元素之前插入新元素。
       當程序調用remove(int index)方法刪除index索引出的元素時,ArrayList同樣也需要對底層數組進行整體移動,但調用remove(int index)刪除集合元素時,ArrayList無須考慮創建新數組,因此執行該方法比ArrayList執行add(int index , Object obj)方法略微快一點。當LinkedList調用remove(int index)方法刪除集合元素時,與調用add(int index , Object obj)開銷幾乎相同。
       當調用add(Object obj)方法向List集合尾端添加一個元素時,大部分時候ArrayList無須對底層數組元素進行整體移動,因此也可以獲得很好的性能(甚至比LinkedList的性能更好);但如果添加這個元素導致集合長度超過了底層數組長度,ArrayList必須創建一個長度爲原來長度1.5倍的數組,再由垃圾回收機制回收原有數組,這樣系統開銷就比較大了。但LinkedList調用add(Object obj)方法添加元素時總可以獲得很好的性能。
       當程序把LinkedList當成雙端隊列、棧使用時,LinkedList可以快速定位需要操作的元素,因此LinkedList總是具有更好的性能表現。
       總而言之,大部分情況之下,ArrayList的性能總是優於LinkedList,因此大部分情況下都應該優先考慮ArrayList集合。但是如果程序經常需要添加、刪除元素,尤其是經常需要調用add(E e)方法向集合中添加元素,則應該考慮使用LinkedList集合。


       




發佈了46 篇原創文章 · 獲贊 44 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章