Java基礎之ArrayList與LinkedList

在Java編程過程中,大家對ArrayList肯定不陌生,但是有沒關注過ArrayList和LinkedList他們的區別呢?最後我們寫的程序可能看似漂亮但是並不高效。那麼我來分享一下ArrayList與LinkedList的區別。從數據結構上看,ArrayList是實現了基於動態數據的結構,而LinkedList是基於實現鏈表的數據結構。而兩種數據結構在程序上提現出來的優缺點就在增刪和改查的速率上。

數據的更新和查找

ArrayList的所有數據是在同一個地址上,而LinkedList的每個數據都有自己的地址,所以在數據進行查找的時候,由於LinkedList的每個數據地址不一樣,get數據的時候ArrayList的速度會優於LinkedList,而更新數據的時候,雖然都是通過循環,循環到指定節點修改數據,但是LinkedList的查詢速度已經是慢的,而且對於LinkedList而言,更新數據時不像ArrayList只需要找到對應的下標更新就好,LinkedList需要修改指針,速率不言而喻。

數據的增加和刪除

對於數據的增加元素,ArrayList是通過移動該元素之後的元素位置,其後元素的位置全部+1,所以耗時較長,而LinkedList只需要將該元素前的後續指針指向該元素並將該元素的後續指針指向之後的元素即可。刪除同理。

下面我們通過程序檢驗結果:

  • 針對增刪
public class JavaDemo {
    public static final int N = 50000;

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        getTime(new ArrayList());
        getTime(new LinkedList());
    }

    static void getTime(List list) {
        insertData(list);
        deleteData(list);
    }

    // 向list的指定位置插入N個元素,並統計時間
    private static void insertData(List list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < N; i++)
            list.add(0, i);
        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + "插入" + N + "條數據耗時:" + interval + " ms");
    }

    // 從list的指定位置刪除N個元素,並統計時間
    private static void deleteData(List list) {
        long startTime = System.currentTimeMillis();
        // 刪除list第一個位置元素
        for (int i = 0; i < N; i++)
            list.remove(0);
        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + "刪除" + N + "條數據耗時" + interval + " ms");
    }

    // 獲取list類型名稱
    private static String getListName(List list) {
        if (list instanceof LinkedList) {
            return "LinkedList";
        } else if (list instanceof ArrayList) {
            return "ArrayList";
        } else {
            return "error";
        }
    }
}

我得到的輸出是:

ArrayList插入50000條數據耗時:169 ms
ArrayList刪除50000條數據耗時160 ms
LinkedList插入50000條數據耗時:4 ms
LinkedList刪除50000條數據耗時1 ms

從這個例子可以看出,當一個元素被加到ArrayList的最開端時,所有已經存在的元素都會後移,這就意味着數據移動和複製上的開銷。相反的,將一個元素加到LinkedList的最開端只是簡單的未這個元素分配一個記錄,然後調整兩個連接。在LinkedList的開端增加一個元素的開銷是固定的,而在ArrayList的開端增加一個元素的開銷是與ArrayList的大小成比例的。刪除同理。然而如果在列表的末端添加數據呢?

    // 向list的末尾位置依次插入N個元素,並統計時間
    private static void insertData(List list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < N; i++)
            list.add(i, i);
        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + "插入" + N + "條數據耗時:" + interval + " ms");
    }

我得到的輸出是:

ArrayList插入50000條數據耗時:4 ms(ArrayList插入50000條數據耗時:5 ms)
LinkedList插入50000條數據耗時:4 ms

我在嘗試多次的情況下,ArrayList會出現不同的結果,LinkedList都一直固定這個結果,但是從上面的結果可以看出,他們所耗的時間都差不多。

  • 針對改查
    // 從list中讀取元素,並統計時間
    private static void readData(List list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < N; i++) {
            list.get(i);
        }
        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + "查詢" + N + "條數據耗時:" + interval + "ms");
    }

    // 從list的隨機位置修改元素,並統計時間
    private static void updateData(List list) {
        long startTime = System.currentTimeMillis();
        int M = 40000;
        for (int i = 0; i < 40000; i++) {
            int j = (int) (1 + Math.random() * (40000 - 1 + 1));
            list.set(j, "list");
        }
        long endTime = System.currentTimeMillis();
        long interval = endTime - startTime;
        System.out.println(getListName(list) + "隨機修改" + M + "條數據耗時" + interval + " ms");
    }

我得到的輸出是:

ArrayList查詢50000條數據耗時:2ms
ArrayList隨機修改40000條數據耗時4 ms
LinkedList查詢50000條數據耗時:883ms
LinkedList隨機修改40000條數據耗時825 ms

這個結果不是固定的,但是基本上ArrayList的時間要明顯小於LinkedList的時間。對一個LinkedList做隨機訪問所消耗的時間與這個list的大小是成比例的。而相應的,在ArrayList中進行隨機訪問所消耗的時間是固定的。

總結

ArrayList和LinkedList的比較

  • ArrayList是實現了基於動態數組的數據結構,LinkedList基於雙鏈表的數據結構。
  • 對於隨機訪問get數據,ArrayList是優於LinkedList,因爲LinkedList要移動指針。
  • 有些說法認爲LinkedList新增和刪除比ArrayList更快,這種說法其實是不夠嚴謹:
    • LinkedList做插入、刪除的時候,慢在尋址,快在只需要改變前後Entry的引用地址。
    • ArrayList做插入、刪除的時候,慢在數組元素的批量copy,快在尋址。

所以,如果待插入、刪除的元素是在數據結構的前半段尤其是非常靠前的位置的時候,LinkedList的效率將大大快過ArrayList,因爲ArrayList將批量copy大量的元素;越往後,對於LinkedList來說,因爲它是雙向鏈表,所以在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差別,但是ArrayList由於要批量copy的元素越來越少,操作速度必然追上乃至超過LinkedList。

從這個分析看出,如果你十分確定你插入、刪除的元素是在前半段,那麼就使用LinkedList;如果你十分確定你刪除、刪除的元素在比較靠後的位置,那麼也可以考慮使用ArrayList。如果你不能確定你要做的插入、刪除是在哪兒呢?那還是建議你使用LinkedList吧,因爲一來LinkedList整體插入、刪除的執行效率比較穩定,沒有ArrayList這種越往後越快的情況;二來插入元素的時候,弄得不好ArrayList就要進行一次擴容,記住,ArrayList底層數組擴容是一個既消耗時間又消耗空間的操作。
另外大家要了解ArrayList與LinkedList的數據類型轉換:
強制數據類型轉換是不行的,因爲類ArrayList與類LinkedList不是父子類關係。
但可以通過構造方法轉換

        ArrayList arrayList = new ArrayList();
        //對arrayList對象添加數據
        LinkedList linkedList = new LinkedList(arrayList);
or
        LinkedList linkedList = new LinkedList();
        //對linkedList對象添加數據
        ArrayList<String> arrayList = new ArrayList(linkedList);

源碼參考:
該文爲本人學習的筆記,方便以後複習用到。總結是參考網上各大帖子,取其精華整合自己的理解而成。至於源碼分析我就不貼了。另外記得這集合框架源碼面試經常會問,大家要理解清楚。

這裏寫圖片描述

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