簡體   English   中英

Java中ArrayList和LinkedList之間的區別 - 性能的原因

[英]Difference between ArrayList and LinkedList in Java - the whys for performance

我認為理解上我理解ArrayList和LinkedList之間的區別非常好。 然而,這是第一次,我把它做了一點測試,測試結果出來了,與我的期望完全不同。

期望 :

  1. 在開頭插入時,Arraylist會比LinkedList慢,因為它必須“移動”元素,對於鏈表,它只是更新2個引用。

    現實:在大多數迭代中都是相同的。 對於選擇的幾次迭代,它更慢。

  2. 在開頭刪除時,Arraylist會比LinkedList慢,因為它必須“移位”元素,對於Linkedlist,它只是使一個元素無效。

    現實:從beg中刪除時性能相同。

測試用例:1,000,000個元素

public static void main(String[] args) {
    int n = 1000000;

    List arrayList = new ArrayList(n+10);
    long milis = System.currentTimeMillis();
    for(int i= 0 ;i<n;i++){
        arrayList.add(i);
    }
    System.out.println("insert arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    List linkedList = new LinkedList();
    milis = System.currentTimeMillis();
    for(int i= 0 ;i<n;i++){
        linkedList.add(i);
    }
    System.out.println("insert linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    //System.out.println("Adding at end");
    milis = System.currentTimeMillis();
    arrayList.add(n-5,n+1);
    System.out.println("APPEND arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.add(n-5,n+1);
    System.out.println("APPEND linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    //add at front
    milis = System.currentTimeMillis();
    arrayList.add(0,0);
    System.out.println("INSERT BEG arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.add(0,0);
    System.out.println("INSERT BEG linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    //add at middle
    milis = System.currentTimeMillis();
    arrayList.add(n/2,n/2);
    System.out.println("INSERT MIDDLE arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.add(n/2,n/2);
    System.out.println("INSERT MIDDLE linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    //get from front, mid, end
    milis = System.currentTimeMillis();
    arrayList.get(0);
    System.out.println("get front arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.get(0);
    System.out.println("get front linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    arrayList.get(n/2);
    System.out.println("get MIDDLE arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.get(n/2);
    System.out.println("get MIDDLE linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    arrayList.get(n-4);
    System.out.println("get last arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.get(n-4);
    System.out.println("get last linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    //delete from front, mid, end.
    milis = System.currentTimeMillis();
    arrayList.remove(0);
    System.out.println("del front arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.remove(0);
    System.out.println("del front linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    arrayList.remove(n/2);
    System.out.println("del mid arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.remove(n/2);
    System.out.println("del mid linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    arrayList.remove(n-4);
    System.out.println("del end arraylist takes "+(System.currentTimeMillis()-milis)+" ms");

    milis = System.currentTimeMillis();
    linkedList.remove(n-4);
    System.out.println("del end linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");

}

輸出日志

insert arraylist takes 141 ms
insert linkedlist takes 312 ms
APPEND arraylist takes 0 ms
APPEND linkedlist takes 0 ms
INSERT BEG arraylist takes 0 ms
INSERT BEG linkedlist takes 0 ms
INSERT MIDDLE arraylist takes 0 ms
INSERT MIDDLE linkedlist takes 0 ms
get front arraylist takes 0 ms
get front linkedlist takes 0 ms
get MIDDLE arraylist takes 0 ms
get MIDDLE linkedlist takes 16 ms
get last arraylist takes 0 ms
get last linkedlist takes 0 ms
del front arraylist takes 0 ms
del front linkedlist takes 0 ms
del mid arraylist takes 0 ms
del mid linkedlist takes 15 ms
del end arraylist takes 0 ms
del end linkedlist takes 0 ms

那是什么原因? 使用JDK 1.6。

編輯:使用System.nanotime()后,它確實給了我預期的答案。 同意,它只是一次試驗,應該是平均的。

insert arraylist takes 137076082 ns
insert linkdlist takes 318985917 ns
APPEND arraylist takes 69751 ns
APPEND linkdlist takes 98126 ns
**INSERT BEG arraylist takes 2027764 ns
INSERT BEG linkdlist takes 53522 ns**
INSERT MIDDLE arraylist takes 1008253 ns
INSERT MIDDLE linkdlist takes 10395846 ns
get front arraylist takes 42364 ns
get front linkdlist takes 77473 ns
get MIDDLE arraylist takes 39499 ns
get MIDDLE linkdlist takes 9645996 ns
get last arraylist takes 46165 ns
get last linkdlist takes 43446 ns
**del front arraylist takes 1720329 ns
del front linkdlist takes 108063 ns**
del mid arraylist takes 1157398 ns
del mid linkdlist takes 11845077 ns
del end arraylist takes 54149 ns
del end linkdlist takes 49744 ns

前兩個(奇怪的)測試數字的解釋是:

插入ArrayList通常較慢,因為一旦達到其邊界就必須增長。 它必須創建一個新的更大的數組,並從原始數組中復制數據。

但是當你創建一個足夠的ArrayList以適應所有插入時(這是你的情況,因為你正在做new ArrayList(n+10) ) - 它顯然不會涉及任何數組復制操作。 添加到它將比使用LinkedList更快,因為LinkedList必須處理它的“鏈接”(指針),而巨大的ArrayList只是在給定(最后)索引處設置值。

此外,您的測試並不好,因為每次迭代都涉及自動裝箱(從int轉換為整數) - 這將花費額外的時間來完成此操作,並且還會因為在第一次傳遞時將填充的整數緩存而搞砸結果。

int n = 1000000; 太小。 你得到0 ms並不意味着它沒有時間來完成插入或刪除。 這意味着經過的時間不到1毫秒。 嘗試上升int n = 1000000;的數量int n = 1000000; 你可以看到差異。

編輯:我誤讀了你的代碼。 我以為你在數組列表的前面插入了n元素。 您絕對應該插入多個項目而不是僅插入一個項目。

另一個編輯:如果插入n元素,則不需要提高n的值。 相反,您可能希望減少它,因為插入ArrayList的前面是一個緩慢的操作。

使用ArrayList,中間操作的插入將是理論上的兩個步驟:

  • O(1)找到位置並插入新值
  • O(n)將剩余的值向前移動。

使用LinkedList,如果尚未知道相關節點(從頭節點循環),則還有兩個步驟:

  • O(n)在該位置找到Node
  • O(1)插入新值

但是,除了預期的算法復雜性之外,還有其他一些點會對實際運行時產生影響:

  • 特定的語言實現可能會使特定進程的某些部分更快。 在這種情況下,ArrayList使用的Java的arraycopy()接口比復制每個值的for循環更快。 對於所有數組(大小,類型)而言可能並非如此 - 同樣,它將是特定於實現的。

  • Big Oh忽略可能對某些數據集產生影響的常量。 例如,冒泡排序是O(n * n)但可以檢測O(n)中的預排序數組,並且對於幾乎排序的數組非常快。

  • LinkedList需要向虛擬機請求更多內存(創建Node對象)。 需要更多內存的進程有時可能會導致瓶頸。

總結一下:謹防理論假設並始終測量,最好用真實世界的數據和操作:)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM