源代碼解讀
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
功能和特點
- 繼承了一個抽象類
AbstractSequentialList
,這個類就是用調用ListIterator
實現了元素的增刪查改,這些方法在LinkedList
中被複寫。 LinkedList
實現了List
、Deque
、Cloneable
以及Serializable
接口。其中Deque
是雙端隊列接口,所以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
表示的是雙向鏈表的節點數據結構。
使用雙向循環鏈表實現,初始化時,雙向循環鏈表的first=last=null
,
添加第一個元素:
即使得firt=last=newNode
。
再次添加元素時:
pred = last;
pred.next = newNode2;
pred.next.prev = pred;
pred = newNode2;
last = pred;
變量
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
size
是元素個數,first
是雙向鏈表的第一個元素,last
雙向鏈表的最後一個元素。
構造函數
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
一個默認的的構造函數;
一個支持添加集合中多有元素到LinkedList
中。
addAll()
方法如下,
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//即爲頭節點,
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
當不指定元素添加的位置時,默認添加到last Node的後面(即index=size
,size表示LInkedList
的元素個數);否則將集合中的所有元素插入到LinkedList
中。
主要是通過`node(int index)找到指定位置的Node對象,具體實現如下:
Node<E> node(int index) {
// assert isElementIndex(index);
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;
}
}
此處技巧,通過index與size向左移一位(即$size/2$)比較,判斷最快找到index位置元素是從頭還是從尾部開始查找,時間複雜度有$O(n)降低爲$O(n/2)+O(1)$。
核心私有方法
LinkedList
內部有幾個關鍵的私有方法,它們實現了鏈表的插入、刪除等操作。
表頭插入
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)//LinkedList中無節點
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
//尾部插入
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) //如果鏈表原來爲空,讓first指向這個唯一的節點
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)//succ是first節點
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//刪除頭節點
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next; //先保存下一個節點
f.item = null;
f.next = null; // help GC
first = next; //讓first指向下一個節點
if (next == null) //如果下一個節點爲空,說明鏈表原來只有一個節點,現在成空鏈表了,要把last指向null
last = null;
else //否則下一個節點的前驅節點要置爲null
next.prev = null;
size--;
modCount++;
return element;
}
//刪除尾節點
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev; //保存前一個節點
l.item = null;
l.prev = null; // help GC
last = prev; //last指向前一個節點
if (prev == null) //與頭節點刪除一樣,判斷是否爲空
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//從鏈表中間刪除節點
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) { //前驅爲空,說明刪除的是頭節點,first要指向下一個節點
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;
}
LinkedList的隊列方法實現和主要的增刪查改都是基於上述幾個方法。
核心public方法
公開的方法幾乎都是調用上面幾個私有方法實現的。 add
public boolean add(E e) {
linkLast(e);
return true;
}
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
這些方法的實現都很簡單。注意最後一個方法add(int index, E element)
,這個方法是在指定的位置插入元素。首先判斷位置是否越界,然後判斷是不是最後一個位置。如果是就直接插入鏈表末尾,否則調用linkBefore(element, node(index)
方法。這裏在傳參數的時候又調用了node(index)
,這個方法的目的是找到這個位置的節點對象。
用於查找指定位置元素的get(int index)方法也是調用node實現的:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
remove
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
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;
}
第一個remove(int index)
方法同樣要調用node(index)
尋找節點。而第二個方法remove(Object o)
是刪除指定元素,這個方法要依次遍歷節點進行元素的比較,最壞情況下要比較到最後一個元素,比調用node方法更慢,時間複雜度爲$O(n)$。
可以看出
LinkedList
的元素可以是null。
總結
LinkedList
基於雙向鏈表實現,元素可以爲null。- 對添加和刪除元素的時間複雜度認爲是$O(1)$,而查找某個元素的時間複雜度較高。適合添加和刪除較頻繁的使用場景。
Thanks for reading! want more