Java 容器源碼分析之 LinkedList

概覽

同 ArrayList 同樣,LinkedList 也是對 List 接口的一種具體實現。不一樣的是,ArrayList 是基於數組來實現的,而 LinkedList 是基於雙向鏈表實現的。LinkedList 類的聲明以下:java

1
2
3
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList 繼承自 AbstractSequentialList,實現了 List 接口,同時還實現了 Deque 接口。AbstractSequentialList 是 AbstractList 的子類,爲基於順序訪問的鏈表提供了一些基本的實現。LinkedList 實現了 Deque 接口,Deque 接口是一種雙端隊列,能夠做爲 FIFO 隊列和 LIFO 隊列(棧)來使用。LinkedList 支持全部元素,包括 null。node

下面基於JDK 8 中的代碼對LinkedList的實現加以分析。數組

底層結構

LinkedList 基於雙向鏈表進行實現,並使用兩個引用 first 和 last 分別指向雙向鏈表的頭尾元素。同 ArrayList 同樣,使用 modCount 來記錄結構化修改的次數,並依此實現 fail-fast 機制。併發

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

雙向鏈表的節點結構以下,分別用 prev 和 next 指向該節點的前驅和後繼結點。ui

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

雙向鏈表操做

LinkedList 提供的全部操做都是在雙向鏈表的基礎上完成的。Dqeue 接口的一些實現就是在 雙向鏈表的兩端進行操做,也是基於對頭和尾部元素的操做。總的來講,雙向鏈表的操做並不複雜,下面簡單地進行解析,大部分操做都是對如下幾種操做的封裝。this

向頭部添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//將一個元素加入雙向鏈表的頭部
private void linkFirst(E e) {
final Node<E> f = first;
//新節點的前驅節點爲null,後繼節點爲原來的頭節點
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null) //原來的頭節點爲空
//新加入的節點是第一個也是最後一個
last = newNode;
else
//修改原來頭節點的前驅指向
f.prev = newNode;
//調整鏈表大小
size++;
//修改計數器
modCount++;
}

向尾部添加元素

1
2
3
4
5
6
7
8
9
10
11
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++;
}

從中間插入元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//在一個非空節點前插入元素
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++;
}

移除頭部節點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//將頭節點從鏈表移除
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
//引用修改成null,方便GC
f.item = null;
f.next = null; // help GC
//調整頭節點
first = next;
if (next == null) //移除後鏈表爲空
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

移除尾部節點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}

移除任意一個非空節點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//移除一個非空節點
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; //後繼

//注意對前驅爲null的處理
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}

//注意對後繼爲null的處理
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}

x.item = null; //GC
size--;
modCount++;
return element;
}

清空鏈表

主要是爲了方便垃圾回收器進行垃圾回收。spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}

根據索引獲取元素

由於是雙向鏈表,可向前遍歷,也可向後遍歷。查找時雙向進行,靠近頭節點則從前向後查找;靠近尾部,則從後向前查找。code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//根據索引獲取元素
Node<E> node(int index) {
// assert isElementIndex(index);
//雙向查找,根據index和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;
}
}

反查一個元素的索引

第一次出現:繼承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

最後一次出現,從後向前查找:索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}

迭代器

經過 next 的指向依次進行遍歷。還提供了反向的迭代(從尾部到頭部),經過 prev 的指向依次遍歷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
//建立迭代器時的modCount,檢測併發修改,fail-fast
private int expectedModCount = modCount;

ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}

public boolean hasNext() {
return nextIndex < size;
}

public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();

lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}

public boolean hasPrevious() {
return nextIndex > 0;
}

public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();

//若是next == null, 前一個爲尾節點
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}

public int nextIndex() {
return nextIndex;
}

public int previousIndex() {
return nextIndex - 1;
}

public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();

Node<E> lastNext = lastReturned.next;
//調用unlink方法移除元素
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
//修改modCount
expectedModCount++;
}

public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}

public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}

public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
//檢查併發修改,fail-fast
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

小結

LinkedList 是 List 接口基於雙向鏈表的一種實現,同時還實現了 Deque 接口,能夠做爲 FIFO 和 LIFO 隊列使用。雙向鏈表的實現使得插入和刪除操做的代價下降,能夠在常數時間內完成;然而查找操做須要遍歷列表,儘管雙向列表使得能夠從兩端進行查找,但在長度較長時仍然須要較長的時間。

在大多數狀況下會選擇使用 ArrayList,儘管插入和刪除代價相較於 LinkedList 更高,但隨機訪問的特性使得在查找方面 ArrayList 比 LinkedList 具備更多的優點。關於 ArrayList 和 LinkedList 的使用選擇上能夠參考 StackOverflow 上的這個問答

相關文章
相關標籤/搜索