目錄
- LinkedList介紹
- Node內部類
- LinkedList源碼分析
LinkedList 介紹
在分析緩存淘汰算法LRU時,雙向鏈表是其中一種實現方式,動手實現時才發現head和tail在沒有其他結點時的處理很彆扭,所以就研究下LinkedList的源碼,找點思路;
首先看看關於LinkedList的簡介
LinkedList 是一個繼承於AbstractSequentialList的雙向鏈表。它也可以被當作堆棧、隊列或雙端隊列進行操作。
LinkedList 實現 List 接口,能對它進行隊列操作。
LinkedList 實現 Deque 接口,即能將LinkedList當作雙端隊列使用。
LinkedList 實現了Cloneable接口,即覆蓋了函數clone(),能克隆。
LinkedList 實現java.io.Serializable接口,這意味着LinkedList支持序列化,能通過序列化去傳輸。
LinkedList 是非同步的。
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;
}
}
Node類很好理解,是個泛型類,由三要素構成:
- 當前結點的前繼結點;
- 當前結點的後續結點;
- 當前結點對應的泛型值;
Node結點在創建實例對象時,就需要對其前後結點以及結點對應的值都初始化;
LinkedList 類源碼
注意理解雙向鏈表的核心設計思路,LinkedList的first和last都只是指向鏈表中的node結點實例對象的引用,first和last自身並沒有被創建成單獨的結點對象,理解這一點很關鍵;
實例變量域
// 當前鏈表的元素個數
transient int size = 0;
protected transient int modCount = 0;
// 指向鏈表的第一個結點
transient Node<E> first;
// 指向鏈表的最後一個結點
transient Node<E> last;
linkLast方法:向鏈表尾部添加結點
/**
* Links e as last element.
*/
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 linkLast(E e) {
final Node<E> l = last = null;
final Node<E> newNode = new Node<>(null, e, null);
last = newNode;
first = newNode;
size++;
modCount++;
}
// ------------------ 當之後再向鏈表中添加結點時 上述方法可簡化爲:-------------------------
void linkLast(E e) {
final Node<E> newNode = new Node<>(last, e, null);
final Node<E> l = last;
last = newNode;
l.next = newNode;
size++;
modCount++;
}
在鏈表爲空時,first和last結點都是指向null;
當第一次向鏈表尾部添加結點時,
- 創建一個新結點,prev和next都指向null;
- 由於此時first和last都沒有任何指向(都爲null),所以講first和last都指向該新結點;
當第二次(i > 1)向鏈表尾部添加結點時,
- 創建一個新結點,prev指向last指向的舊的尾結點,next指向null;
- last重新指向新尾結點;
- last之前指向的尾結點,將其next指向新的尾結點;
linkFirst方法:向鏈表頭部添加結點
/**
* Links e as first element.
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
當第一次向鏈表頭部添加結點時,
- 創建一個新結點,prev和next都指向null;
- 由於此時first和last都沒有任何指向(都爲null),所以講first和last都指向該新結點;
當第二次(i > 1)向鏈表頭部添加結點時,
- 創建一個新結點,prev指向null,next指向first指向的舊的頭結點;
- first重新指向新的頭結點;
- 舊的頭結點的prev指向新的頭結點;
unlink方法:從鏈表中移除一個指定的非null結點
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
// 如果node是第一個結點,就將第二個結點作爲first
first = next;
} else {
// 如果node不是第一個結點,就將前驅結點next指向後繼結點;
// 並剪斷x對前驅結點的引用,有利於GC回收
prev.next = next;
x.prev = null;
}
if (next == null) {
// 如果node是最後一個結點,就將倒數第二個結點作爲最後一個結點
last = prev;
} else {
// 如果node不是最後一個結點,就將後繼結點前驅指向前驅結點;
// 並簡單x對後繼結點的引用,有利於GC回收
next.prev = prev;
x.next = null;
}
// 截斷x對item泛型內容的引用,有利於GC回收,至此x結點已經徹底剪斷對所有資源的引用
x.item = null;
size--;
modCount++;
// 從鏈表中刪除x結點後,返回對x的item內容的引用,以備他用
return element;
}
remove方法:根據指定的內容,刪除對應的結點
// remove根據指定的內容o,來移除相應的結點,remove是基於unlink方法實現移除功能;
// remove支持移除結點item爲null的情景
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}