Java 容器和泛型(2)ArrayList 、LinkedList和Vector比較

泥沙磚瓦漿木匠-Jeff_Li發表於2015-04-08

一、List回顧

序列(List),有序的Collection,正如它的名字一樣,是一個有序的元素列表。確切的講,列表通常允許滿足 e1.equals(e2) 的元素對 e1 和 e2,並且如果列表本身允許 null 元素的話,通常它們允許多個 null 元素。實現List的有:ArrayList、LinkedList、Vector、Stack等。值得一提的是,Vector在JDK1.1的時候就有了,而List在JDK1.2的時候出現,待會我們會聊到ArrayList和Vector的區別。

二、ArrayList vs. Vector

ArrayList是一個可調整大小的陣列實現的序列。隨著元素增加,其大小會動態的增加。此類在Iterator或ListIterator迭代中,呼叫容器自身的remove和add方法進行修改,會丟擲ConcurrentModificationException併發修改異常。

注意,此實現不是同步的。如果多個執行緒同時訪問一個 ArrayList 例項,而其中至少一個執行緒從結構上修改了列表,那麼它必須 保持外部同步。(結構上的修改是指任何新增或刪除一個或多個元素的操作,或者顯式調整底層陣列的大小;僅僅設定元素的值不是結構上的修改。)這一般通過對自然封裝該列表的物件進行同步操作來完成。如果不存在這樣的物件,則應該使用 Collections.synchronizedList 方法將該列表“包裝”起來。這最好在建立時完成,以防止意外對列表進行不同步的訪問:

List list = Collections.synchronizedList(new ArrayList(…)); 

下面演示下相關ArrayList例子。

ArrayList基本方法程式碼:

@SuppressWarnings({ "rawtypes", "unchecked" })
    public static void listMethods()
    {

        List a1 = new ArrayList<String>();

        a1.add("List01");
        a1.add("List03");
        a1.add("List04");
        System.out.print("原來集合:/n/t"+a1+"/n");

        a1.add(1,"List02");
        System.out.print("指定角標1插入:/n/t"+a1+"/n");

        a1.remove(2);
        System.out.print("指定角標2刪除:/n/t"+a1+"/n");

        System.out.print("指定角標2查詢:/n/t"+a1.get(2)+"/n");

        Iterator i1 = a1.iterator();
        System.out.println("用迭代器查詢全部元素:");
        while (i1.hasNext())
        {
            System.out.print(i1.next()+",");
        }
    }

可以從控制檯可以看出:

原來集合:
    [List01, List03, List04]
指定角標1插入:
    [List01, List02, List03, List04]
指定角標2刪除:
    [List01, List02, List04]
指定角標2查詢:
    List04
用迭代器查詢全部元素:
List01,List02,List04

在上面我們可以根據角標來增加(add)、刪除(remove)、獲取(get)列表裡面元素。ArrayList提供了Iterator迭代器來遍歷序列。值得注意的是,迭代器的就相當於一個指標指向角標,next()方法就相當於指標往後移一位。所以切記,用迭代器中一次迴圈用一次next()。

下面演示下在ConcurrentModificationException的出現,及處理方案。泥瓦匠用Iterator演示這個異常的出現:

@SuppressWarnings({ “unchecked”, “rawtypes” })
    public static void iteratorTest()
    {
        List a1 = new ArrayList<String>();

        a1.add(“List01″);
        a1.add(“List02″);
        a1.add(“List04″);
        a1.add(“List05″);

        Iterator i1 = a1.iterator();
        while (i1.hasNext())
        {
            Object obj = i1.next();
            if (obj.equals(“List02″))
                a1.add(“List03″);
        }

        System.out.print(“集合:/n/t”+a1+”/n”);
    }

執行,我們可以在控制檯看到:

Java 容器 &#038; 泛型:二、ArrayList 、LinkedList和Vector比較

怎麼解決的,先看清楚這個問題。問題描述很清楚,在建立迭代器之後,除非通過迭代器自身的 remove 或 add 方法從結構上對列表進行修改,否則在任何時間以任何方式對列表進行修改,迭代器都會丟擲ConcurrentModificationException

因此我們應該這樣修改程式碼,用ListIterator迭代器提供方法:

@SuppressWarnings({ "unchecked", "rawtypes" })
    public static void listIterator()
    {

        List a1 = new ArrayList<String>();

        a1.add("List01");
        a1.add("List");
        a1.add("List03");
        a1.add("List04");

        ListIterator l1 = a1.listIterator();
        while (l1.hasNext())
        {
            Object obj = l1.next();
            if (obj.equals("List"))
            {
                l1.remove();
                l1.add("List02");
            }
        }
        System.out.print("集合:/n/t"+a1+"/n");
    }

執行下,我們可以看到:

集合:
    [List01, List02, List03, List04]

這樣,我們成功解決了這個併發修改異常。把其中‘List’元素刪除,新增了一個‘List02’的元素。

Vector非常類似ArrayList。早在JDK1.1的時候就出現了,以前沒有所謂的List介面,現在此類被改進為實現List介面。但與新的Collection不同的是,Vector是同步的。泥瓦匠想說的是Vector,在像查詢的效能上會比ArrayList開銷大。下面演示下Vector的基本例子:

@SuppressWarnings({ "unchecked", "rawtypes" })
    public static void vectorMethods()
    {
        Vector v1 = new Vector<String>();

        v1.add("Vector001");
        v1.add("Vector002");
        v1.add("Vector003");
        v1.add("Vector004");
        v1.add("Vector005");

        Enumeration e1 =v1.elements();
        while (e1.hasMoreElements())
        {
            Object object = e1.nextElement();
            System.out.println(object);
        }
    }

從方法上看幾乎沒差別,同樣注意的是:此介面的功能與 Iterator 介面的功能是重複的。此外,Iterator 介面新增了一個可選的移除操作,並使用較短的方法名。新的實現應該優先考慮使用 Iterator 介面而不是 Enumeration 介面。

三、LinkedList及其與ArrayList效能比

LinkedList與ArrayList一樣實現List介面,LinkedList是List介面連結串列的實現。基於連結串列實現的方式使得LinkedList在插入和刪除時更優於ArrayList,而隨機訪問則比ArrayList遜色些。LinkedList實現所有可選的列表操作,並允許所有的元素包括null。除了實現 List 介面外,LinkedList 類還為在列表的開頭及結尾 get、remove 和 insert 元素提供了統一的命名方法。這些操作允許將連結列表用作堆疊、佇列或雙端佇列

LinkedList和ArrayList的方法時間複雜度總結如下圖所示。

Java 容器 &#038; 泛型:二、ArrayList 、LinkedList和Vector比較

表中,新增add()指新增元素的方法,remove()是指除去(int index)角標。ArrayList具有O(N)的任意指數時間複雜度的新增/刪除,但O(1)的操作列表的末尾。連結串列的O(n)的任意指數時間複雜度的新增/刪除,但O(1)操作端/列表的開始。

泥瓦匠用程式碼驗證下這個結論:

public static void testPerBtwnArlAndLkl()
    {
        ArrayList<Integer> arrayList   = new ArrayList<Integer>();
        LinkedList<Integer> linkedList = new LinkedList<Integer>();

        // ArrayList add
        long startTime  = System.nanoTime();
        long endTime;
        long duration;

        for (int i = 0; i < 100000; i++) {
            arrayList.add(i);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("ArrayList add:  " + duration);

        // LinkedList add
        startTime = System.nanoTime();

        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("LinkedList add: " + duration);

        // ArrayList get
        startTime = System.nanoTime();

        for (int i = 0; i < 10000; i++) {
            arrayList.get(i);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("ArrayList get:  " + duration);

        // LinkedList get
        startTime = System.nanoTime();

        for (int i = 0; i < 10000; i++) {
            linkedList.get(i);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("LinkedList get: " + duration);

        // ArrayList remove
        startTime = System.nanoTime();

        for (int i = 9999; i >=0; i--) {
            arrayList.remove(i);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("ArrayList remove:  " + duration);

        // LinkedList remove
        startTime = System.nanoTime();

        for (int i = 9999; i >=0; i--) {
            linkedList.remove(i);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("LinkedList remove: " + duration);
    }

控制檯輸出如下:

ArrayList add:  16904776
LinkedList add: 12015418
ArrayList get:  1304593
LinkedList get: 108950741
ArrayList remove:  787388127
LinkedList remove: 128145950

對比下的話,其效能差距很明顯。LinkedList在新增和刪除中效能快,但在獲取中效能差。從複雜度和測試結果,我們應該懂得平時在新增或者刪除操作頻繁的地方,選擇LinkedList時考慮:

1、沒有大量的元素的隨機訪問

2、新增/刪除操作

自然我下面用LinedList實現一個資料結構–棧。泥瓦匠留給大家LinkedList的一些方法自己消化下。

package com.sedion.bysocket.collection;
import java.util.LinkedList;

/**
 * 用LinkedList實現棧
 * 佇列和棧區別:佇列先進先出,棧先進後出。
 */
public class Stack<T>
{
    private LinkedList<T> storage = new LinkedList<T>();

    /** 入棧 */
    public void push(T v)
    {
        storage.addFirst(v);
    }

    /** 出棧,但不刪除 */
    public T peek()
    {
        return storage.getFirst();
    }

    /** 出棧,刪除 */
    public T pop()
    {
        return storage.removeFirst();
    }

    /** 棧是否為空 */
    public boolean empty()
    {
        return storage.isEmpty();
    }

    /** 輸出棧元素 */
    public String toString()
    {
        return storage.toString();
    }

    public static void main(String[] args)
    {
        Stack stack=new Stack<String>();
        stack.push("a");
        stack.push("b");
        stack.push("c");
        System.out.println(stack.toString());
        Object obj=stack.peek();
        System.out.println(obj+"--"+stack.toString());
        obj=stack.pop();
        System.out.println(obj+"--"+stack.toString());
        System.out.println(stack.empty());
    }
}

四、總結

泥瓦匠總結如下:

Vector和ArrayList

1、vector是執行緒同步的,所以它也是執行緒安全的,而arraylist是執行緒非同步的,是不安全的。

2、記住併發修改異常 java.util.ConcurrentModificationException ,優先考慮ArrayList,除非你在使用多執行緒所需。

Aarraylist和Linkedlist
1、對於隨機訪問get和set,ArrayList覺得優於LinkedList,LinkedList要移動指標。
2、於新增和刪除操作add和remove,LinedList比較佔優勢,ArrayList要移動資料。
3、單條資料插入或刪除,ArrayList的速度反而優於LinkedList.若是批量隨機的插入刪除資料,LinkedList的速度大大優於ArrayList. 因為ArrayList每插入一條資料,要移動插入點及之後的所有資料。

相關文章