你肯定 LinkedList 在新增/刪除元素時,效率比 ArrayList 高?

在面試的時候都會被問到集合相關的問題,好比:你能講講 ArrayList 和 LinkedList 的區別嗎?java

那麼我相信你確定可以答上來:ArrayList 是基於數組實現的, LinkedList 是基於鏈表實現的node

接下來面試官就會連環問了,那你能講講,它們都用在什麼場景下嗎?web

阿粉知道這種程度確定難不倒我們讀者的:由於 ArrayList 是基於數組實現的,因此在遍歷的時候, ArrayList 的效率是要比 LinkedList 高的, LinkedList 是基於鏈表實現的,因此在進行新增/刪除元素的時候, LinkedList 的效率是要比 ArrayList 高的面試

面試官:哦哦,好的,我大概瞭解了,我這邊沒有什麼想問的了,您回去等消息能夠嗎數組

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

???發生了什麼?微信

哈哈,上面模擬了一個面試場景,是想引出來這篇文章的主題:LinkedList 在新增/刪除元素時,效率比 ArrayList 高,這是真的嗎?app

我相信你也知道套路,通常這麼一問,那確定就不是真的了dom

放一張圖片,這是通過我測試以後的真實結果ide

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

由於微信不能放外鏈的緣故,能夠在公衆號後臺發送 「20200821」 獲取測試代碼測試

ArrayList 與 LinkedList 新增元素比較

從圖中能夠看出來, LinkedList 在新增元素時,它的效率不必定比 ArrayList 高,這是要分狀況的

若是是從集合頭部位置新增元素的話,那確實是 LinkedList 的效率要比 ArrayList 高

可是若是是從集合中間位置或者是尾部位置新增元素, ArrayList 效率反而要比 LinkedList 效率要高

Excuse me ?居然和我之前學的不同?阿粉我學的淺,你別騙我

640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

哈哈哈,爲何會這樣呢

這是由於 ArrayList 是基於數組實現的嘛,而數組是一塊連續的內存空間,因此在添加元素到數組頭部時,須要對頭部後面的數據進行復制重排,因此效率是蠻低的

可是 LinkedList 是基於鏈表實現的,在添加元素的時候,首先會經過循環查找到添加元素的位置,若是要添加的位置處於 List 前半段,那就從前向後找;若是位置在後半段,那就從後往前找,因此 LinkedList 添加元素到頭部是很是高效的(小聲 BB ,這我知道

哦,這你知道?看來基礎蠻不錯的嘛~

640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1

因此當 ArrayList 在添加元素到數組中間時,有一部分數據須要複製重排,效率就不是很高,那爲啥 LinkedList 比它還要低呢?這是由於 LinkedList 把元素添加到中間位置的時候,須要在添加以前先遍歷查找,這個查找的時間比較耗時

添加元素到尾部操做中, ArrayList 的效率要比 LinkedList 的還要高,這是爲啥嘞

由於 ArrayList 在添加的時候不須要什麼操做,直接插入就行了,因此效率蠻高的

可是 LinkedList 就不同了,對於 LinkedList 來講,也不須要查找啥的,直接插入就能夠了,可是須要 new 對象,還有變換指針指向對象呀,這些過程耗時加起來可就比 ArrayList 長了

它是有前提的,那就是 ArrayList 初始化容量是足夠的狀況下,纔有上述的特色,若是 ArrayList 涉及到動態擴容,那它的效率確定會下降

ArrayList 與 LinkedList 刪除元素比較

刪除元素和新增元素的原理是同樣的,因此刪除元素的操做和新增元素的操做耗時也是很相近

這裏就再也不贅述

ArrayList 與 LinkedList 遍歷元素比較

測試結果很是明顯,對於 LinkedList 來講,若是使用 for 循環的話,效率特別低,可是 ArrayList 使用 for 循環去遍歷的話,就比較高

爲啥呢?

emmm ,得從源碼提及

先來看 ArrayList 的源碼吧,這個比較簡單

public class ArrayList<Eextends AbstractList<E>
        implements List<E>, RandomAccessCloneablejava.io.Serializable

可以看到, ArrayList 實現了 List , RandomAccess , Cloneable 還有 Serializable 接口

你是否是對 RandomAccess 這個接口挺陌生的?這是個啥?

可是經過查閱源碼可以發現它也只是個空的接口罷了,那 ArrayList 爲啥還要去實現它嘞

由於 RandomAccess 接口是一個標誌接口,它標識着「只要實現該接口的 list 類,均可以實現快速隨機訪問」

實現快速隨機訪問?你能想到什麼?這不就是數組的特性嘛!能夠直接經過 index 來快速定位 & 讀取

那你是否是就能想到, ArrayList 是數組實現的,因此實現了 RandomAccess 接口, LinkedList 是用鏈表實現的,因此它沒有用 RandomAccess 接口實現吧?

beautiful ~就是這樣

咱瞅瞅 LinkedList 源碼

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneablejava.io.Serializable

果真,跟我們設想的同樣,沒有實現 RandomAccess 接口

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

那爲啥 LinkedList 接口使用 for 循環去遍歷的時候,慢的不行呢?

我們瞅瞅 LinkedList 在 get 元素時,都幹了點兒啥

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

在 get 方法中,主要調用了 node() 方法,由於 LinkedList 是雙向鏈表,因此 if (index < (size >> 1)) 在判斷 i 是在前半段仍是後半段,若是是前半段就正序遍歷,若是是在後半段那就倒序遍歷,那麼爲何使用 for 循環遍歷 LinkedList 時,會這麼慢?(好像離真相愈來愈近了

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

緣由就在兩個 for 循環之中,以第一個 for 循環爲例

  • get(0) :直接拿到了node0 地址,而後拿到 node0 的數據

  • get(1) :先拿到 node0 地址,而後 i < index ,開始拿 node1 的地址,符合條件,而後去拿 node1 的數據

  • get(2) :先拿到 node0 的地址,而後 i < index ,拿到 node1 的地址, i < index ,繼續向下走,拿到 node2 的地址,符合條件,獲取 node2 的數據

發現問題了嘛?我就是想要 2 的數據, LinkedList 在遍歷時,將 0 和 1 也給遍歷了,若是數據量很是大的話,那效率可不就唰唰的下來了嘛

那到如今,我們也就很是明確了,若是是要遍歷 ArrayList 的話,最好是用 for 循環去作,若是要遍歷 LinkedList 的話,最好是用迭代器去作

我猜你必定會說,阿粉啊,那若是對方就給我傳過來了一個 list ,我不知道它是 ArrayList 仍是 LinkedList 呀?我該怎麼辦呢

還記得 ArrayList 和 LinkedList 有什麼不一樣嗎?是否是 ArrayList 實現了 RandomAccess 接口,可是 LinkedList 沒有實現,因此能夠從這點去着手解決

暖暖的阿粉在這裏給個簡單的小 demo ,能夠參考下:

public class ListTest {
    public static void main(String[] args) {
        List<String> arrayList = new ArrayList<String>();
        arrayList.add("aaa");
        arrayList.add("bbb");
        isUseIterator(arrayList);

        List<String> linkedList = new LinkedList<String>();
        linkedList.add("ccc");
        linkedList.add("ddd");
        isUseIterator(linkedList);
    }

    public static void isUseIterator(List list){
        if (list instanceof RandomAccess){
            System.out.println("實現了 RandomAccess 接口,使用 for 循環遍歷");

            for (int i = 0 ; i < list.size(); i++ ){
                System.out.println(list.get(i));
            }
        }else{
            System.out.println("沒有實現 RandomAccess 接口,使用迭代器遍歷");

            Iterator it = list.iterator();
            while (it.hasNext()){
                System.out.println(it.next());
            }
        }
    }

因此,乖,下次面試官再問你 LinkedList 在新增/刪除元素時,效率比 ArrayList 高嗎,不要再傻傻的回答是了,拿阿粉這篇文章和他扯皮,保證沒問題

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

相關文章
相關標籤/搜索