[Java]ArrayList、LinkedList、Vector、Stack的比較

1、介紹

先回顧一下List的框架圖

由圖中的繼承關係,能夠知道,ArrayList、LinkedList、Vector、Stack都是List的四個實現類。java

  • AbstractList是一個抽象類,它繼承於AbstractCollection。AbstractList實現List接口中除size()、get(int location)以外的函數。數組

  • AbstractSequentialList 是一個抽象類,它繼承於AbstractList。AbstractSequentialList 實現了「鏈表中,根據index索引值操做鏈表的所有函數」。安全

  • ArrayList 是一個數組隊列,至關於動態數組。它由數組實現,隨機訪問效率高,隨機插入、隨機刪除效率低。多線程

  • LinkedList 是一個雙向鏈表。它也能夠被看成堆棧、隊列或雙端隊列進行操做。LinkedList隨機訪問效率低,但隨機插入、隨機刪除效率低。
  • Vector 是矢量隊列,和ArrayList同樣,它也是一個動態數組,由數組實現。可是ArrayList是非線程安全的,而Vector是線程安全的。
  • Stack 是棧,它繼承於Vector。它的特性是:先進後出(FILO, First In Last Out)。框架

    2、性能測試

    在對ArrayList、LinkedList、Vector、Stack進行比較以前,咱們先來對他們進行一個性能測試,結合源碼和測試結果來對ArrayList、LinkedList、Vector、Stack進行詳細的分析。函數

import java.util.*;

public class ListTest {
    private static final int COUNT = 100000;

    private static LinkedList linkedList = new LinkedList();
    private static ArrayList arrayList = new ArrayList();
    private static Vector vector = new Vector();
    private static Stack stack = new Stack();

    public static void main(String[] args) {
        // 換行符
        System.out.println();
        // 插入
        insertByPosition(stack) ;
        insertByPosition(vector) ;
        insertByPosition(linkedList) ;
        insertByPosition(arrayList) ;

        // 換行符
        System.out.println();
        // 隨機讀取
        readByPosition(stack);
        readByPosition(vector);
        readByPosition(linkedList);
        readByPosition(arrayList);

        // 換行符
        System.out.println();
        // 刪除 
        deleteByPosition(stack);
        deleteByPosition(vector);
        deleteByPosition(linkedList);
        deleteByPosition(arrayList);
    }

    // 獲取list的名稱
    private static String getListName(List list) {
        if (list instanceof LinkedList) {
            return "LinkedList";
        } else if (list instanceof ArrayList) {
            return "ArrayList";
        } else if (list instanceof Stack) {
            return "Stack";
        } else if (list instanceof Vector) {
            return "Vector";
        } else {
            return "List";
        }
    }

    // 向list的指定位置插入COUNT個元素,並統計時間
    private static void insertByPosition(List list) {
        long startTime = System.currentTimeMillis();

        // 向list的位置0插入COUNT個數
        for (int i=0; i<COUNT; i++)
            list.add(0, i);

        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + " : insert "+COUNT+" elements into the 1st position use time:" + interval+" ms");
    }

    // 從list的指定位置刪除COUNT個元素,並統計時間
    private static void deleteByPosition(List list) {
        long startTime = System.currentTimeMillis();

        // 刪除list第一個位置元素
        for (int i=0; i<COUNT; i++)
            list.remove(0);

        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + " : delete "+COUNT+" elements from the 1st position use time:" + interval+" ms");
    }

    // 根據position,不斷從list中讀取元素,並統計時間
    private static void readByPosition(List list) {
        long startTime = System.currentTimeMillis();

        // 讀取list元素
        for (int i=0; i<COUNT; i++)
            list.get(i);

        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + " : read "+COUNT+" elements by position use time:" + interval+" ms");
    }
}

獲得的結果以下性能

Stack : insert 100000 elements into the 1st position use time:834 ms
Vector : insert 100000 elements into the 1st position use time:818 ms
LinkedList : insert 100000 elements into the 1st position use time:10 ms
ArrayList : insert 100000 elements into the 1st position use time:822 ms

Stack : read 100000 elements by position use time:5 ms
Vector : read 100000 elements by position use time:3 ms
LinkedList : read 100000 elements by position use time:6088 ms
ArrayList : read 100000 elements by position use time:2 ms

Stack : delete 100000 elements from the 1st position use time:857 ms
Vector : delete 100000 elements from the 1st position use time:835 ms
LinkedList : delete 100000 elements from the 1st position use time:6 ms
ArrayList : delete 100000 elements from the 1st position use time:849 ms

根據結果,能夠很明顯的看出ArrayList、LinkedList、Vector、Stack的性能有很大的區別。測試

操做 ArrayList LinkedList Vector Stack
讀取 2ms 6088ms 3ms 5ms
插入 822ms 10ms 818ms 834ms
刪除 849ms 6ms 835ms 857ms

讀取:ArrayList > Vector > Stack > LinkedList
插入:LinkedList > Vector > ArrayList > Stack
刪除:LinkedList > Vector > ArrayList > Stack線程

3、插入的分析

LinkedList

// 在index前添加節點,且節點的值爲element
public void add(int index, E element) {
    addBefore(element, (index==size ? header : entry(index)));
}

// 獲取雙向鏈表中指定位置的節點
private Entry<E> entry(int index) {
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+
                                            ", Size: "+size);
    Entry<E> e = header;
    // 獲取index處的節點。
    // 若index < 雙向鏈表長度的1/2,則從前向後查找;
    // 不然,從後向前查找。
    if (index < (size >> 1)) {
        for (int i = 0; i <= index; i++)
            e = e.next;
    } else {
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}

// 將節點(節點數據是e)添加到entry節點以前。
private Entry<E> addBefore(E e, Entry<E> entry) {
    // 新建節點newEntry,將newEntry插入到節點e以前;而且設置newEntry的數據是e
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    // 插入newEntry到鏈表中
    newEntry.previous.next = newEntry;
    newEntry.next.previous = newEntry;
    size++;
    modCount++;
    return newEntry;
}

從中,咱們能夠看出:經過add(int index, E element)向LinkedList插入元素時。先是在雙向鏈表中找到要插入節點的位置index;找到以後,再插入一個新節點。
雙向鏈表查找index位置的節點時,有一個加速動做:若index < 雙向鏈表長度的1/2,則從前向後查找; 不然,從後向前查找。指針

ArrayList

// 將e添加到ArrayList的指定位置
public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);

    ensureCapacity(size+1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
         size - index);
    elementData[index] = element;
    size++;
}

在這裏面有一個很是耗時的操做

System.arraycopy(elementData, index, elementData, index + 1, size - index);
該方法被標記了native,調用了系統的C/C++代碼,在JDK中是看不到的,但在openJDK中能夠看到其源碼。
該函數實際上最終調用了C語言的memmove()函數,所以它能夠保證同一個數組內元素的正確複製和移動,比通常的複製方法的實現效率要高不少,很適合用來批量處理數組。Java強烈推薦在複製大量數組元素時用該方法,以取得更高的效率。

Vector

public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }
    public void add(int index, E element) {
        insertElementAt(element, index);
    }

能夠看到Vector和ArrayList是同樣的,都調用了System.arraycopy。因爲Stack和繼承與Vector,就不仔細分析了。

4、查找的分析

LinkedList

LinkedList隨機訪問的代碼
// 返回LinkedList指定位置的元素
public E get(int index) {
    return entry(index).element;
}

// 獲取雙向鏈表中指定位置的節點
private Entry<E> entry(int index) {
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+
                                            ", Size: "+size);
    Entry<E> e = header;
    // 獲取index處的節點。
    // 若index < 雙向鏈表長度的1/2,則從前前後查找;
    // 不然,從後向前查找。
    if (index < (size >> 1)) {
        for (int i = 0; i <= index; i++)
            e = e.next;
    } else {
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}

從中,咱們能夠看出:經過get(int index)獲取LinkedList第index個元素時。先是在雙向鏈表中找到要index位置的元素;找到以後再返回。
雙向鏈表查找index位置的節點時,有一個加速動做:若index < 雙向鏈表長度的1/2,則從前向後查找; 不然,從後向前查找。

ArrayList

// 獲取index位置的元素值
public E get(int index) {
    RangeCheck(index);

    return (E) elementData[index];
}

private void RangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);
}

咱們能夠看到ArrayList直接返回數組中index位置的元素,而不須要像LinkedList同樣進行查找。
經過源碼發現Vector和Stack的操做方式和ArrayList同樣,這裏就不詳細分析了。

5、刪除的分析

LinkedList

private E remove(Entry<E> e) {
    if (e == header)
        throw new NoSuchElementException();
    E result = e.element;
    e.previous.next = e.next;
    e.next.previous = e.previous;
    e.next = e.previous = null;
    e.element = null;
    size--;
    modCount++;

    return result;
}

因爲刪除了某一節點所以調整相應節點的先後指針信息,以下:

e.previous.next = e.next;//預刪除節點的前一節點的後指針指向預刪除節點的後一個節點。 
e.next.previous = e.previous;//預刪除節點的後一節點的前指針指向預刪除節點的前一個節點。

清空預刪除節點:

e.next = e.previous = null;
e.element = null;

交給gc完成資源回收,刪除操做結束。

與ArrayList比較而言,LinkedList的刪除動做不須要「移動」不少數據,從而效率更高。

ArrayList

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

恩,又是調用了System.arraycopy。

6、結論

操做 ArrayList LinkedList Vector Stack
讀取 O(1) O(n) O(1) O(1)
插入 O(n) O(1) O(n) O(n)
刪除 O(n) O(1) O(n) O(n)
  • ArrayList(實現動態數組),查詢快(隨意訪問或順序訪問),增刪慢。總體清空快,線程不一樣步(非線程安全)。數組長度是可變的百分之五十延長
  • LinkedList(實現鏈表),查詢慢,增刪快。
  • Vector(實現動態數組),都慢,被ArrayList替代。長度任意延長。線程安全(同步的類,函數都是synchronized)
  • Stack(實現堆棧)繼承於Vector,先進後出。

因此,快速訪問ArrayList,快速增刪LinkedList,單線程均可以用,多線程只能用同步類Vector

相關文章
相關標籤/搜索