先看再點贊,給本身一點思考的時間,微信搜索【沉默王二】關注這個靠才華苟且的程序員。
本文 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。
LinkedList<String> list = new LinkedList<>();
複製代碼
和建立 ArrayList 同樣,能夠經過上面的語句來建立一個字符串類型的 LinkedList(經過尖括號來限定 LinkedList 中元素的類型,若是嘗試添加其餘類型的元素,將會產生編譯錯誤)。
不過,LinkedList 沒法在建立的時候像 ArrayList 那樣指定大小。
能夠經過 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)
方法將元素添加到指定的位置。
可使用 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()
方法會對下標進行一個初步的判斷,若是靠近末端,則從最後開始遍歷,這樣可以節省很多遍歷的時間,小夥伴們眼睛要睜大點了,這點要學。
找到節點後,再替換新值並返回舊值。
能夠經過 remove()
方法刪除指定位置上的元素:
list.remove(1);
複製代碼
該方法會調用 unlink()
方法對先後節點進行更新。
E 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()
方法刪除第一個節點和最後一個節點。
若是要正序查找一個元素,可使用 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()
方法用於返回但不刪除第一個元素。
若是要咱們本身實現一個鏈表的話,上面這些增刪改查的輪子方法是必定要白嫖啊,不對,必定要借鑑啊。
上一篇 ArrayList 中提到過,隨機訪問一個元素的時間複雜度爲 O(1),但 LinkedList 要複雜一些,由於數據增大多少倍,耗時就增大多少倍,由於要循環遍歷,因此時間複雜度爲 O(n)。
至於 LinkedList 在插入、添加、刪除元素的時候有沒有比 ArrayList 更快,這要取決於數據量的大小,以及元素所在的位置。不過,從理論上來講,因爲不須要移動數組,應該會更快一些。但到底快不快,下一篇帶來答案,小夥伴們敬請期待。
我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注便可提高學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,奧利給。
注:若是文章有任何問題,歡迎絕不留情地指正。
若是你以爲文章對你有些幫助歡迎微信搜索「沉默王二」第一時間閱讀,回覆「小白」更有我肝了 4 萬+字的 Java 小白手冊 2.0 版,本文 GitHub github.com/itwanger 已收錄,歡迎 star。