上一篇咱們經過簡單的ArrayList的源碼分析,分析了ArrayList爲何有序,爲何讀操做快?爲何寫操做慢?詳情請穿越 jdk1.8-Java集合框架之二-ArrayList源碼分析node
這篇咱們講解的是LinkedList,一樣咱們是帶着問題來的:數組
能夠看出,LinkedList繼承了一個父類AbstractSequentialList,另外實現了4個接口,分別是Cloneable、List、Deque、Serializable。bash
父類AbstractSequentialList是一個抽象類,提供部分已經實現的功能,單與本次講解的LinkedList內容關係不大,暫且不談,有興趣的同窗能夠自行了解,數據結構
回想一下ArrayList的源碼,發現ArrayList實現的接口與LinkedList實現的接口差異就在於ArrayList實現了RandomAccess接口而LinkedList沒有,LinkedList實現了Deque接口而ArrayList沒有。框架
what?Deque接口又是什麼鬼??dom
public interface Deque<E> extends Queue<E>
複製代碼
Deque接口是Queue接口的子接口,且從名字上咱們能夠猜出,應該是雙向隊列。源碼分析
從繼承關係圖能夠得出兩個結論,1:LinkedList沒有實現RandomAccess接口說明LinkedList不支持快速訪問;2.LinkedList實現了Deque接口說明是一個雙向隊列的數據結構。post
//長度
transient int size = 0;
//指向第一個節點的指針
transient Node<E> first;
//指向最後一個節點的指針
transient Node<E> last;
複製代碼
就這樣完了??
LinkedList只定義了三個實例變量,沒有其餘的了。既然這樣,那咱們就先撩一下看這個Node是哪一個小妖精。。ui
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
複製代碼
其實這個Node類是LinkedList的私有靜態內部類。這個類定義了三個屬性,一個是與LinkedList範型一致的item,另外兩個屬性類型都是Node,next指向Node對象的下一個Node對象,prev指向Node對象的前一個Node對象。this
很明顯,Node類這樣的設計,咱們能夠回答一開始帶來的第一個問題,LinkedList就是一個鏈表的數據結構設計,每個對象都持有指向前一個對象的引用,也都持有指向後一個對象的引用,這種結構天生就是有序的。
先從構造方法入手,LinkedList提供了兩個構造方法,無參數的構造方法沒有任何實現,咱們直接看有參數的構造方法:
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
複製代碼
該構造方法須要傳入一個Collection集合實現類對象,直接將集合對象放進addAll方法裏,咱們再看allAll方法:
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
複製代碼
addAll方法又調用了addAll的重載方法,並傳入了兩個參數,第一個是LinkedList的實例變量size,第二個是構造方法傳進來的Collection集合對象。繼續看addAll的這個重載方法:
public boolean addAll(int index, Collection<? extends E> c) {
//檢查index是否合法
checkPositionIndex(index);
//將Collection實例轉爲Object數組
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
//index等於size時,只有兩種狀況:
//1.LinkedList中沒有元素
//2.要添加的元素位置等於LinkedList長度
if (index == size) {
succ = null;
pred = last;
} else {
//尋找index位置存在的元素
succ = node(index);
//找到index位置存在的元素的前一個元素
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//將對象包裝成一個Node對象
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
//若是index位置前一個元素爲null,表示當前添加的是第一個元素
//將當前元素的包裝對象設置爲第一個
first = newNode;
else
//不然設置index位置前一個元素的下一個元素爲當前新添加的元素
pred.next = newNode;
//添加完成,須要將pred設置爲新添加的元素
pred = newNode;
}
if (succ == null) {
//若是添加前長度爲0
//或者添加元素的位置在LinkedList長度以外
//那麼LinkedList最後元素應該是最後添加的元素
last = pred;
} else {
//否者表示在已有元素中間添加元素
//那麼最後添加的元素的下一個元素應該是添加前index位置對應的元素
pred.next = succ;
//添加前index位置的元素,設置前一個元素爲最後添加的元素
succ.prev = pred;
}
//LinkedList長度增長
size += numNew;
modCount++;
return true;
}
複製代碼
核心代碼的邏輯相對ArrayList複雜了點,但也是比較簡單的。其實整個LinkedList的結構就像俄羅斯套娃,每層套娃都用線連着外面和裏面的套娃。
那麼針對第二個問題,LinkedList爲何是讀效率低,寫效率高呢,咱們接着往下看:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
//由於LinkedList實現了Deque接口,因此LinkedList仍是雙向隊列
//因此此處查找元素,最大的查找範圍是最大長度的1/2
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;
}
}
複製代碼
還記得ArrayList的讀操做嗎,是經過數組下標直接找到對應元素,複雜度是O(1),而LinkedList是鏈表結構的,並無下標,因此只能從頭至尾遍歷,或者從尾到頭遍歷查找,複雜度是O(n),因此LinkedList相對ArrayList來講,讀效率很低。
那麼爲何又說寫效率高呢??咱們接着看:
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
複製代碼
上面代碼不難看出,LinkedList添加元素,其實只須要設置添加的元素的先後元素引用,並將先後元素的相關屬性設置爲所須要添加的元素的引用,就完成了元素的添加。同理,刪除元素,只須要移除元素,並將元素的先後元素相關屬性從新設置,則完成刪除操做。整個LinkedList的單個元素寫操做,最多涉及3個元素的修改。
相對ArrayList添加或者刪除元素都須要從新計算容量甚至擴容,再將部分甚至所有元素複製再移位的操做來講,LinkedList的寫操做效率實在高太多。
對於最後一個問題,講到目前爲止,相信不用說你們都知道了。LinkedList容許添加劇復的值,也容許添加null值,由於LinkedList並非直接維護添加的元素,而是將添加的元素包裝成Node對象來維護,因此添加什麼元素都無所謂了。
LinkedList:有序、雙向鏈表、可重複、讀效率低、寫效率高。
適用場景:業務頻繁寫操做。