我們上一次說到List的ArrayList,我們這次去看下LinkedList---顧名思義是鏈表,鏈表的優點就不用說了吧,增刪效率比較高(具體的朋友們上網看吧),先來看下LinkedList的整體構架:
首先我們看到了LinkedList間接的實現了List接口(說明LinkedList是有list的特性的,add,remove等)、實現了Cloneable(可複製)、Serializable(進行了序列化),除此之外還有一個東西還實現了Queue(隊列,說明應該是有隊列的一些特性,pop等),這次先不側重queue的東西,我們主要看LinkedList是如何實現鏈表的。
//鏈表的長度
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
//鏈表的頭
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
//鏈表的尾
transient Node<E> last;
//鏈表維護的Node的結構
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;
}
}
我們可以看到,我們鏈表維護了一個size、first(頭)和last(尾),我們可以看到Node的結構中有個item、next、prev充分說明了此鏈表是一個雙向鏈表。
public static void main(String[] args){
//初始化linkedList
List<String> linkedList = new LinkedList<>();
//添加元素
linkedList.add("abc");
linkedList.add("bcd");
return;
}
這個空的構造函數沒有什麼內容,所以我們就跳過去,我們直接看add方法具體做的什麼。
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
//看方法註釋,我們可以看到是將該元素添加到此鏈表的end,返回true
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
//首先取出last節點
final Node<E> l = last;
//將newNode的prev指向last,後節點指向null,因爲newNode永遠都是作爲last的,所以next指向null
final Node<E> newNode = new Node<>(l, e, null);
//last指向newNode
last = newNode;
//如果鏈表爲null的話則first也指向newNode,此時鏈表只有一個節點first和last都指向一個
//如果鏈表部位null,只需要將last的next指向這個newNode就行了,這樣就形成了雙向鏈表
if (l == null)
first = newNode;
else
l.next = newNode;
//最後size++
size++;
modCount++;
}
LinkedList就是這樣一個節點一個節點關聯起來的,我們可以看一下下面的圖片(這裏偷個懶,網上隨便找了一張):
我們接下來看一下remove(int index)刪除指定位置對象的方法,其中個人覺得有個有趣的地方,所以給大家分享下:
/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//檢查index是否超出範圍size
checkElementIndex(index);
//刪除指定位置的節點,首先得找到這個節點
return unlink(node(index));
}
//檢查index是否正確
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Returns the (non-null) Node at the specified element index.
*/
//返回指定index位置的節點
Node<E> node(int index) {
// assert isElementIndex(index);
//首先去比較index和size >> 1(也就是size的一半),如果比中間數小則從鏈表頭找,否則從尾找
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;
}
}
我們可以看到,我們想要移除指定位置的節點,首先得找到這個節點(這個是重點),關鍵我們的鏈表如何找到指定位置?不知道大家有沒有自己比較好的方法,我們來看下jdk的思路(我覺得jdk的實現方法很有趣),node(int index)方法中的if條件拿index和size的一半比較,文字解釋太麻煩了,我來畫個圖吧:
假設:我們linkedList的長度爲6,如要remove的index爲3,此時我們3 < (size >> 1)爲false,則我們從linkedList後面開始直到變量i>index,此時i變量位置的就是index位置的node,如果我們index是2,那麼是從前面開始的,我們大家想這樣其實是把linkedList從中間分爲2個,速度是不是比沒分之前快兩倍呢,哈哈哈~
總結:linkedList爲雙鏈表,維護的是一個first和last指針,而每個節點有item自身、prev和next兩個節點來維護雙鏈表的關係,其他的功能都是圍繞我們的雙鏈表來進行的,有興趣的大家可以仔細研究一下源碼,有時會發現很有趣的小細節哦~