ArrayList和LinkedList各自實現和區別

之前做項目經常會用到集合,對於ArrayList和LinkedList的使用,也沒有做過多的思考,現在開始看java集合的源碼,發現ArrayList和LinkedList兩者之間的區別還是很大的。這裏根據自己看的集合源碼和大神的博客分享對兩者的實現和區別進行總結:


1、java集合源碼分析(推薦給看源碼很吃力的朋友們): https://www.cnblogs.com/skywang12345/p/3323085.html#top

2、ArrayList和LinkedList各自的實現和區別:https://cnblogs.com/skywang12345/p/3323085.html#top


步入正題:

ArrayList,LinkedList,Vestor這三個類都實現了java.util.List接口,但它們有各自不同的特性,主要如下: 

一、同步性 

ArrayList,LinkedList是不同步的,而Vector是同步的【這裏的不同步指的是。當使用線程和web的時候,對這個集合對象進行操作,那麼不同的線程,和不同的web客戶端所獲取的這個集合對象是不同的。所以是說不同步,且不安全的】所以如果不要求線程安全的話,可以使用ArrayList或LinkedList,可以節省爲同步而耗費的開銷。但在多線程的情況下,有時候就不得不使用Vector了。當然,也可以通過一些辦法包裝ArrayList,LinkedList,使他們也達到同步,但效率可能會有所降低。 


二、數據增長 

從內部實現機制來講ArrayList和Vector都是使用Object的數組形式來存儲的。當你向這兩種類型中增加元素的時候,如果元素的數目超出了內部數組目前的長度它們都需要擴展內部數組的長度,Vector缺省情況下自動增長原來一倍的數組長度,ArrayList是原來的50%,所以最後你獲得的這個集合所佔的空間總是比你實際需要的要大。所以如果你要在集合中保存大量的數據那麼使用Vector有一些優勢,因爲你可以通過設置集合的初始化大小來避免不必要的資源開銷。 

三、檢索、插入、刪除對象的效率 

ArrayList和Vector中,從指定的位置(用index)檢索一個對象,或在集合的末尾插入、刪除一個對象的時間是一樣的,可表示爲O(1)。但是,如果在集合的其他位置增加或移除元素那麼花費的時間會呈線形增長:O(n-i),其中n代表集合中元素的個數,i代表元素增加或移除元素的索引位置。爲什麼會這樣呢?以爲在進行上述操作的時候集合中第i和第i個元素之後的所有元素都要執行(n-i)個對象的位移操作。 

LinkedList中,在插入、刪除集合中任何位置的元素所花費的時間都是一樣的—O(1),但它在索引一個元素的時候比較慢,爲O(i),其中i是索引的位置。


在此,我們總結一下ArrayList和LinkedList的大致區別: 
     1.ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。 
     2.對於隨機訪問get和set,ArrayList絕對優於LinkedList,因爲LinkedList要移動指針。 
     3.對於新增和刪除操作add和remove,LinedList比較佔優勢,因爲ArrayList要移動數據。 

     ArrayList和LinkedList是兩個集合類,用於存儲一系列的對象引用(references)。例如我們可以用ArrayList來存儲一系列的String或者Integer。


     那麼ArrayList和LinkedList在性能上有什麼差別呢?什麼時候應該用ArrayList什麼時候又該用LinkedList呢?  我們下面就來解答這個問題:

一.時間複雜度 

首先一點關鍵的是,ArrayList的內部實現基於基礎的對象數組,因此,它使用get方法訪問列表中的任意一個元素時 (random access),時間複雜度是O(1)它的速度要比LinkedList快。LinkedList中的get方法是按照順序從列表的一端開始檢查,直到另外一端。對 LinkedList而言,時間複雜度是O(i),訪問列表中的某個指定元素沒有更快的方法了。 

以下通過代碼來演示使用get方法訪問列表時的時間複雜度對比:

假設我們有一個很大的列表,它裏面的元素已經排好序了,這個列表可能是ArrayList類型的也可能是LinkedList類型的,現在我們對這個列表來進行二分查找(binary search),比較列表是ArrayList和LinkedList時的查詢速度,看下面的程序: 

import java.util.LinkedList;   
import java.util.List;   
import java.util.Random;   
import java.util.ArrayList;   
import java.util.Arrays;   
import java.util.Collections;   
public class TestList {   
     public static final int N=50000;   
     public static List values;   
  
     static{   
         Integer vals[]=new Integer[N];   
         Random r=new Random();   
         for(int i=0,currval=0;i<N;i++){   
             vals=new Integer(currval);   
             currval+=r.nextInt(100)+1;   
         }   
         values=Arrays.asList(vals);   
     }   
  
     static long timeList(List lst){   
         long start=System.currentTimeMillis();   
         for(int i=0;i<N;i++)...{   
             int index=Collections.binarySearch(lst, values.get(i));   
             if(index!=i)   
                 System.out.println("***錯誤***");   
         }   
         return System.currentTimeMillis()-start;   
     }   
     public static void main(String args[]){   
         System.out.println("ArrayList消耗時間:"+timeList(new ArrayList(values)));   
         System.out.println("LinkedList消耗時間:"+timeList(new LinkedList(values)));   
     }   
}   

輸出:

        ArrayList消耗時間:15 
        LinkedList消耗時間:2596 

這個結果不是固定的,但是基本上ArrayList的時間要明顯小於LinkedList的時間。因此在這種情況下不宜用LinkedList。二分查找法使用的隨機訪問(random access)策略,而LinkedList是不支持快速的隨機訪問的。對一個LinkedList做隨機訪問所消耗的時間與這個list的大小是成比例的。而相應的,在ArrayList中進行隨機訪問所消耗的時間是固定的。

這是否表明ArrayList總是比LinkedList性能要好呢?這並不一定,在某些情況 下LinkedList的表現要優於ArrayList,有些算法在LinkedList中實現時效率更高。比方說,利用 Collections.reverse方法對列表進行反轉時,其性能就要好些。 


看這樣一個例子,加入我們有一個列表,要對其進行大量的插入和刪除操作,在這種情況下 LinkedList就是一個較好的選擇。請看如下一個極端的例子,我們重複的在一個列表的開端插入一個元素: 

import java.util.*;   
public class ListDemo {   
     static final int N=50000;   
     static long timeList(List list){   
         long start=System.currentTimeMillis();   
         Object o = new Object();   
         for(int i=0; i<N; i++)   
             list.add(0, o);   
         return System.currentTimeMillis()-start;   
         }   
         public static void main(String[] args) {   
             System.out.println("ArrayList耗時:"+timeList(new ArrayList()));   
             System.out.println("LinkedList耗時:"+timeList(new LinkedList()));   
         }   
}   

輸出結果:

              ArrayList耗時:2463 
              LinkedList耗時:15 

這和前面一個例子的結果截然相反,當一個元素被加到ArrayList的最開端時,所有已經存在的元素都會後移,這就意味着數據移動和複製上的開銷。相反的,將一個元素加到LinkedList的最開端只是簡單的未這個元素分配一個記錄,然後調整兩個連接。在 LinkedList的開端增加一個元素的開銷是固定的,而在ArrayList的開端增加一個元素的開銷是與ArrayList的大小成比例的。


二.空間複雜度 
在LinkedList中有一個私有的內部類,定義如下: 

private static class Entry {   
    Object element;   
    Entry next;   
    Entry previous;   
} 

每個Entry對象reference列表中的一個元素,同時還有在LinkedList中它的上一個元素和下一個元素。一個有1000個元素的LinkedList對象將 有1000個鏈接在一起的Entry對象,每個對象都對應於列表中的一個元素。這樣的話,在一個LinkedList結構中將有一個很大的空間開銷,因爲 它要存儲這1000個Entity對象的相關信息。

ArrayList使用一個內置的數組來存儲元素,這個數組的起始容量是10.當數組需要增長時,新的容量按如下公式獲得:新容量=(舊容量*3)/2+1,也就是說每一次容量大概會增長50%。這就意味着,如果你有一個包含大量元素的ArrayList對象, 那麼最終將有很大的空間會被浪費掉,這個浪費是由ArrayList的工作方式本身造成的。如果沒有足夠的空間來存放新的元素,數組將不得不被重新進行分配以便能夠增加新的元素。對數組進行重新分配,將會導致性能急劇下降。如果我們知道一個ArrayList將會有多少個元素,我們可以通過構造方法來指定容量。我們還可以通過trimToSize方法在ArrayList分配完畢之後去掉浪費掉的空間。


三.總結 
ArrayList和LinkedList在性能上各有優缺點,都有各自所適用的地方,總的說來可以描述如下: 

1.對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。對 ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是統一的,分配一個內部Entry對象。 

2.在ArrayList的 中間插入或刪除一個元素意味着這個列表中剩餘的元素都會被移動;而在LinkedList的中間插入或刪除一個元素的開銷是固定的。 

3.LinkedList不支持高效的隨機元素訪問。

4.ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間 。

可以這樣說:當操作是在一列數據的後面添加數據而不是在前面或中間,並且需要隨機地訪問其中的元素時,使用ArrayList會提供比較好的性能;當你的操作是在一列數據的前面或中間添加或刪除數據,並且按照順序訪問其中的元素時,就應該使用LinkedList了。 所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那麼使用Vector或ArrayList都可以。如果是對其它指定位置的插入、刪除操作,最好選擇LinkedList!

【ArrayList和LinkedList的區別有點類似與我們熟知的數組和鏈表之間的區別】

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