目錄
1.數據結構
LinkedList的底層數據結構是一個雙向鏈表,但與大部分教材中的雙向鏈表有些不同。
注意:(1)linkedList中的雙向鏈表沒有頭指針都是節點;(2)第一個節點的前驅爲null,最後一個節點的後驅爲null。
而大部分教材中的雙向鏈表是這樣的。
2.屬性
//節點個數
transient int size = 0;
//記錄第一個節點
transient Node<E> first;
//記錄最後一個節點
transient Node<E> last;
3.Node類
LinkedList的數據結構是一個雙向鏈表,而鏈表都是由節點組成的,Node類就是描述節點的類。
結構圖
源碼
private static class Node<E> {
//如上圖所示
E item;
Node<E> next;
Node<E> prev;
//有參構造,創建一個節點,比如是x,且x的prev指向prev這個節點,
//x的next指向next這個節點,x的item是element
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
有參構造圖解
在鏈表中插入節點的過程就是新增節點與鏈表中節點建立鏈接的過程,假設要在第一個節點與第二節點之間新增一個節點。這裏不太明白的可以先看後面,再回頭來理解這裏。
第一步:
注意:上面那個節點用虛線,代表還沒有創建,準備創建。
第二步:
當我們在創建Node類時,將上圖中的m和n作爲初始化參數,即這樣寫
Node< E> newNode = new Node<>(m, e, n); ,那麼我們一次性就建立了兩條鏈接。
注意:源碼中增加節點就是用的這個邏輯。之後只用再建立兩條鏈接,這個新節點就成功插入了,到這裏已經明白了大半。
4.輔助方法
void linkFirst(E e)、 void linkLast(E e)、void linkBefore(E e, Node< E> succ)、E unlinkFirst(Node< E> f)、E unlinkLast(Node< E> l)、E unlink(Node< E> x)、和Node< E> node(int index) 這七個方法是實現LinkedList核心功能的基礎。
例如:
public void addFirst(E e) {
linkFirst(e);
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
(1)void linkFirst(E e)
方法名要做到見名知意,那個這個方法的功能是:鏈接到第一個
源碼
private void linkFirst(E e) {
//用f記錄第一個節點
final Node<E> f = first;
//創建一個新的節點,且調用的是有參構造,如圖一
final Node<E> newNode = new Node<>(null, e, f);
//fist是用來記錄第一個節點的,所以要變化,如圖二
first = newNode;
if (f == null)
//若f爲null,代表之前鏈表爲空,
//那麼新增一個節點後,該節點也是最後一個節點
last = newNode;
else
//圖三
f.prev = newNode;
//節點個數加1
size++;
modCount++;
}
圖解
數無形時少直覺我們直接上圖說明。
圖一:
圖二:
圖三:
(2)void linkLast(E e)
鏈接到最後
源碼
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;
//節點個數加1
size++;
modCount++;
}
圖解
圖一:
圖二:
圖三:
(3)void linkBefore(E e, Node< E> succ)
在succ節點前插入一個節點
源碼
void linkBefore(E e, Node<E> succ) {
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;
//節點個數加1
size++;
modCount++;
}
圖解
圖一:
圖二:
圖三:
注意:可以看出這個方法是在鏈表中間插入新節點的基礎。
(4)E unlinkFirst(Node< E> f)
解除第一個節點的鏈接
源碼
private E unlinkFirst(Node<E> f) {
//如圖一所示
final E element = f.item;
final Node<E> next = f.next;
//這兩行代碼是方便垃圾回收器回收這個節點
//圖二
f.item = null;
f.next = null;
//圖三
//first屬性是用來記錄鏈表第一個節點的,
//當第一個節點刪除後,first也要移動。
first = next;
if (next == null)
last = null;
else
//圖四
next.prev = null;
//節點個數減1
size--;
modCount++;
return element;
}
圖解
圖一:
圖二:
圖三:
圖四:
(5)E unlinkLast(Node< E> l)
解除最後一個節點的鏈接
源碼
private E unlinkLast(Node<E> l) {
//下面兩行代碼,如圖一所示
final E element = l.item;
final Node<E> prev = l.prev;
//下面三行代碼,如圖二
l.item = null;
l.prev = null;
//last屬性同理first屬性
last = prev;
if (prev == null)
first = null;
else
//圖三
prev.next = null;
size--;
modCount++;
return element;
}
圖解
圖一:
圖二:
圖三:
(6)E unlink(Node x)
解決鏈表中間節點的鏈接
源碼
E unlink(Node<E> x) {
//下面三行代碼,效果如圖一
final E element = x.item;
final Node<E> next = x.next;
final 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;
}
圖解
圖一:
圖二:
圖三:
(7)Node< E> node(int index)
定位方法,給出下標,幫你在鏈表找到這個節點
源碼
Node<E> node(int index) {
//“>> 1”相當於除以2
//這是一個優化,說明JDK底層不會從頭到尾遍歷的,
//而是用二分的思想,判斷更靠近第一個節點first還是last
if (index < (size >> 1)) {
//靠近第一個節點first,就從first開始往後遍歷
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//更靠近最後一個節點last,從last開始往前遍歷
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}