技術小菜比入坑 LinkedList,i 了 i 了

先看再點贊,給本身一點思考的時間,微信搜索【沉默王二】關注這個靠才華苟且的程序員。
本文 GitHub github.com/itwanger 已收錄,裏面還有一線大廠整理的面試題,以及個人系列文章。java

上一篇入坑了 ArrayList,小夥伴們反響不錯,那這篇就繼續入坑 LinkedList,它倆算是親密無間的兄弟,相愛相殺的那種,不離不棄的那種,介紹了這個就必須介紹那個的那種。node

明目張膽地告訴你們一個好消息,我寫了一份 4 萬多字的 Java 小白手冊,小夥伴們能夠在「沉默王二」公衆號後臺回覆「小白」獲取免費下載連接。以爲不錯的話,請隨手轉發給身邊須要的小夥伴,贈人玫瑰,手有餘香哈。git

最開始學習 Java 的時候,我還挺納悶的,有了 ArrayList,幹嗎還要 LinkedList 啊,都是 List,不是不少餘嗎?當時真的很傻很天真,不知道有沒有同款小夥伴。搞不懂二者之間的區別,什麼場景下該用 ArrayList,什麼場景下該用 LinkedList,傻傻分不清楚。那麼這篇文章,能夠一腳把這種天真踹走了。程序員

和數組同樣,LinkedList 也是一種線性數據結構,但它不像數組同樣在連續的位置上存儲元素,而是經過引用相互連接。github

LinkedList 中的每個元素均可以稱之爲節點(Node),每個節點都包含三個項目:其一是元素自己,其二是指向下一個元素的引用地址,其三是指向上一個元素的引用地址。web

Node 是 LinkedList 類的一個私有的靜態內部類,其源碼以下所示:面試

private static class Node<E{
    E item;
    LinkedList.Node<E> next;
    LinkedList.Node<E> prev;

    Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
複製代碼

LinkedList 看起來就像下面這個樣子:算法

  • 第一個節點因爲沒有前一個節點,因此 prev 爲 null;數組

  • 最後一個節點因爲沒有後一個節點,因此 next 爲 null;微信

  • 這是一個雙向鏈表,每個節點都由三部分組成,先後節點和值。

那可能有些小夥伴就會和當初的我同樣,好奇地問,「爲何要設計 LinkedList 呢?」若是能給 LinkedList 類的做者打個電話就行了,惋惜沒有他的聯繫方式。很遺憾,只能靠我來給你們解釋一下了。

第一,數組的大小是固定的,即使是 ArrayList 能夠自動擴容,但依然會有必定的限制:若是聲明的大小不足,則須要擴容;若是聲明的大小遠遠超出了實際的元素個數,又會形成內存的浪費。儘管擴容的算法已經很是優雅,儘管內存已經綽綽有餘。

第二,數組的元素須要連續的內存位置來存儲其值。這就是 ArrayList 進行刪除或者插入元素的時候成本很高的真正緣由,由於咱們必須移動某些元素爲新的元素留出空間,好比說:

如今有一個數組,十、十二、1五、20、四、五、100,若是須要在 12 的位置上插入一個值爲 99 的元素,就必須得把 12 之後的元素日後移動,爲 99 這個元素騰出位置。

刪除是一樣的道理,刪除以後的全部元素都必須往前移動一次。

LinkedList 就擺脫了這種限制:

第一,LinkedList 容許內存進行動態分配,這就意味着內存分配是由編譯器在運行時完成的,咱們無需在 LinkedList 聲明的時候指定大小。

第二,LinkedList 不須要在連續的位置上存儲元素,由於節點能夠經過引用指定下一個節點或者前一個節點。也就是說,LinkedList 在插入和刪除元素的時候代價很低,由於不須要移動其餘元素,只須要更新前一個節點和後一個節點的引用地址便可。

LinkedList 類的層次結構以下圖所示:

  • LinkedList 是一個繼承自 AbstractSequentialList 的雙向鏈表,所以它也能夠被看成堆棧、隊列或雙端隊列進行操做。

  • LinkedList 實現了 List 接口,因此能對它進行隊列操做。

  • LinkedList 實現了 Deque 接口,因此能將 LinkedList 看成雙端隊列使用。

明白了 LinkedList 的一些理論知識後,咱們來看一下如何使用 LinkedList。

0一、如何建立一個 LinkedList

LinkedList<String> list = new LinkedList<>();
複製代碼

和建立 ArrayList 同樣,能夠經過上面的語句來建立一個字符串類型的 LinkedList(經過尖括號來限定 LinkedList 中元素的類型,若是嘗試添加其餘類型的元素,將會產生編譯錯誤)。

不過,LinkedList 沒法在建立的時候像 ArrayList 那樣指定大小。

0二、向 LinkedList 中添加一個元素

能夠經過 add() 方法向 LinkedList 中添加一個元素:

LinkedList<String> list = new LinkedList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("沉默王四");
複製代碼

感興趣的小夥伴能夠研究一下 add() 方法的源碼,它在添加元素的時候會調用 linkLast() 方法。

void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
複製代碼

添加第一個元素的時候,last 爲 null,建立新的節點(next 和 prev 都爲 null),而後再把新的節點賦值給 last 和 first;當添加第二個元素的時候,last 爲第一個節點,建立新的節點(next 爲 null,prev 爲第一個節點),而後把 last 更新爲新的節點,first 保持不變,第一個節點的 next 更新爲第二個節點;以此類推。

還能夠經過 addFirst() 方法將元素添加到第一位;addLast() 方法將元素添加到末尾;add(int index, E element) 方法將元素添加到指定的位置。

0三、更新 LinkedList 中的元素

可使用 set() 方法來更改 LinkedList 中的元素,須要提供下標和新元素。

list.set(0"沉默王五");
複製代碼

來看一下 set() 方法的源碼:

public E set(int index, E element) {
    checkElementIndex(index);
    LinkedList.Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}
複製代碼

該方法會先對指定的下標進行檢查,看是否越界,而後根據下標查找節點:

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

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

node() 方法會對下標進行一個初步的判斷,若是靠近末端,則從最後開始遍歷,這樣可以節省很多遍歷的時間,小夥伴們眼睛要睜大點了,這點要學。

找到節點後,再替換新值並返回舊值。

0四、刪除 LinkedList 中的元素

能夠經過 remove() 方法刪除指定位置上的元素:

 list.remove(1);
複製代碼

該方法會調用 unlink() 方法對先後節點進行更新。

unlink(LinkedList.Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final LinkedList.Node<E> next = x.next;
    final LinkedList.Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}
複製代碼

還可使用 removeFirst()removeLast() 方法刪除第一個節點和最後一個節點。

0五、查找 LinkedList 中的元素

若是要正序查找一個元素,可使用 indexOf() 方法;若是要倒序查找一個元素,可使用 lastIndexOf() 方法。

來看一下 indexOf() 方法的源碼:

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (LinkedList.Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (LinkedList.Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}
複製代碼

基本上和 ArrayList 的大差不差,都須要遍歷,若是要查找的元素爲 null,則使用「==」操做符,能夠避免拋出空指針異常;不然使用 equals() 方法進行比較。

另外,getFirst() 方法用於獲取第一個元素;getLast() 方法用於獲取最後一個元素;poll()pollFirst() 方法用於刪除並返回第一個元素(兩個方法儘管名字不一樣,但方法體是徹底相同的);pollLast() 方法用於刪除並返回最後一個元素;peekFirst() 方法用於返回但不刪除第一個元素。

0六、最後

若是要咱們本身實現一個鏈表的話,上面這些增刪改查的輪子方法是必定要白嫖啊,不對,必定要借鑑啊。

上一篇 ArrayList 中提到過,隨機訪問一個元素的時間複雜度爲 O(1),但 LinkedList 要複雜一些,由於數據增大多少倍,耗時就增大多少倍,由於要循環遍歷,因此時間複雜度爲 O(n)。

至於 LinkedList 在插入、添加、刪除元素的時候有沒有比 ArrayList 更快,這要取決於數據量的大小,以及元素所在的位置。不過,從理論上來講,因爲不須要移動數組,應該會更快一些。但到底快不快,下一篇帶來答案,小夥伴們敬請期待。


我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注便可提高學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,奧利給

注:若是文章有任何問題,歡迎絕不留情地指正。

若是你以爲文章對你有些幫助歡迎微信搜索「沉默王二」第一時間閱讀,回覆「小白」更有我肝了 4 萬+字的 Java 小白手冊 2.0 版,本文 GitHub github.com/itwanger 已收錄,歡迎 star。

相關文章
相關標籤/搜索