書接上一篇ArrayList源碼解析,這一節(jié)繼續(xù)分析LinkedList在Java8中的實現(xiàn),它同樣實現(xiàn)了List接口,不過由名字就可以知道,內(nèi)部實現(xiàn)是基于鏈表的,而且是雙向鏈表,所以Linked List在執(zhí)行像插入或者刪除這樣的操作,效率是極高的,相對地,在隨機訪問方面就弱了很多。
本文基于JDK1.8中LinkedList源碼分析
類定義
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
由上圖以及類定義片段可知,LinkedList繼承了AbstractSequentialList并且實現(xiàn)List,Deque,Cloneable, Serializable接口。
其中,AbstractSequentialList相較于AbstractList(ArrayList的父類)
,只支持次序訪問,而不支持隨機訪問,因為它的
get(int index)
,set(int index, E element)
, add(int index, E element)
, remove(int index)
都是基于迭代器實現(xiàn)的。所以在LinkedList使用迭代器遍歷更快,而ArrayList使用get (i)更快。
接口方面,LinkedList多繼承了一個Deque接口,所以實現(xiàn)了雙端隊列的一系列方法。
基本數(shù)據(jù)結(jié)構(gòu)
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
LinkedList中主要定義了頭節(jié)點指針,尾節(jié)點指針,以及size用于計數(shù)鏈表中節(jié)點個數(shù)。那么每一個Node的結(jié)構(gòu)如何呢?
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; //當前節(jié)點值
this.next = next; //后繼節(jié)點
this.prev = prev;//前驅(qū)節(jié)點
}
}
可以看出,這是一個典型的雙向鏈表的節(jié)點。
LinkedList先不從初始化聊起,首先談插入與刪除。
獲取節(jié)點
獲取節(jié)點是相對比較簡單的操作, LinkedList提供了:
- getFirst獲取頭節(jié)點
- getLast獲取尾節(jié)點
- get(int index) 獲取指定位置的節(jié)點
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
檢查非空后,直接返回first節(jié)點的item
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
檢查非空后,直接返回last節(jié)點的item
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
首先檢查index范圍,然后調(diào)用node(index)獲取index處的節(jié)點,返回該節(jié)點的item值。
看看node(index)的實現(xiàn),后面很多地方借助于這個小函數(shù):
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //判斷index是在鏈表偏左側(cè)還是偏右側(cè)
Node<E> x = first;
for (int i = 0; i < index; i++) //從左邊往右next
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--) //從右往左prev
x = x.prev;
return x;
}
}
由上面可以看出,在鏈表中找一個位置,只能通過不斷遍歷。
另外還有IndexOf,LastIndexOf操作,找出指定元素在LinkedList中的位置:
也是一個從前找,一個從后找,只分析下IndexOf操作:
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;
}
主要也是不斷遍歷,找到值相等的節(jié)點,返回它的Index。
更改節(jié)點的值
主要也就是一個set函數(shù)
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
根據(jù)index找到指定節(jié)點,更改它的值,并且會返回原有值。
插入節(jié)點
LinkedList實現(xiàn)了Deque接口,支持在鏈表頭部和尾部插入元素:
public void addFirst(E e) {
linkFirst(e);
}
public void addLast(E e) {
linkLast(e);
}
這里我們可以看到內(nèi)部實現(xiàn)的函數(shù)是linkFirst
,linkLast
,
鏈表頭插入元素:
private void linkFirst(E e) {
final Node<E> f = first; //現(xiàn)將原有的first節(jié)點保存在f中
final Node<E> newNode = new Node<>(null, e, f); //將新增節(jié)點包裝成一個Node節(jié)點,同時該節(jié)點的next指向之前的first
first = newNode; //將新增的節(jié)點設成first
if (f == null)
last = newNode; //如果原來的first就是空,那么新增的newNode同時也是last節(jié)點
else
f.prev = newNode; //如果不是空,則原來的first節(jié)點的前置節(jié)點就是現(xiàn)在新增的節(jié)點
size++; //插入元素后,節(jié)點個數(shù)加1
modCount++; //還記得上一篇講述的快速失敗嗎?這邊依然是這個作用
}
主要流程:
- 將原有頭節(jié)點保存到f
- 將插入元素包裝成新節(jié)點,并且該新節(jié)點的next指向原來的頭節(jié)點,即f
- 如果原來的頭節(jié)點f為空的話,那么新插的頭節(jié)點也是last節(jié)點,否則,還要設置f的前置節(jié)點為NewNode,即NewNode現(xiàn)在是first節(jié)點了
- 記得增加size,記錄修改次數(shù)modCount
鏈表尾插入元素
void linkLast(E e) {
final Node<E> l = last; //將尾節(jié)點保存到l中
final Node<E> newNode = new Node<>(l, e, null); //把e包裝成Node節(jié)點,同時把該節(jié)點的前置節(jié)點設置為l
last = newNode; //把新插的節(jié)點設置為last節(jié)點
if (l == null)
first = newNode; //如果原來的last節(jié)點為空,那么新增的節(jié)點在意義上也是first節(jié)點
else
l.next = newNode; //否則的話還要將newNode設為原有l(wèi)ast節(jié)點的后繼節(jié)點,所以newNode現(xiàn)在是新的Last節(jié)點
size++;
modCount++;
}
在指定index處插入元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
首先調(diào)用checkPositionIndex檢查index值是否在范圍內(nèi),如果index在最后的話,就調(diào)用在尾部插入的函數(shù),否則調(diào)用LinkBefore,主要看LinkBefore如何實現(xiàn)的:
void linkBefore(E e, Node<E> succ) { //在succ節(jié)點前插入newNode
// assert succ != null;
final Node<E> pred = succ.prev; //將succ的前置節(jié)點記為pred
final Node<E> newNode = new Node<>(pred, e, succ); 以pred為前置節(jié)點,以succ為后繼節(jié)點建立newNode
succ.prev = newNode; //將new Node設為succ的前置節(jié)點
if (pred == null)
first = newNode; //如果原有的succ的前置節(jié)點為空,那么新插入的newNode就是first節(jié)點
else
pred.next = newNode; // 否則,要把newNode設為原來pred節(jié)點的后置節(jié)點
size++;
modCount++;
}
其余幾個常用的add方法也是基于以上函數(shù):
public boolean add(E e) {
linkLast(e);
return true;
}
add
函數(shù)默認在函數(shù)尾部插入元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
addAll(Collection<? extends E> c)
指的是在list尾部插入一個集合,具體實現(xiàn)又依賴于addAll(size,c)
,指的是在指定位置插入一個集合:
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 檢查index位置是否超出范圍
Object[] a = c.toArray(); //將集合c轉(zhuǎn)變成Object數(shù)組 同時計算數(shù)組長度
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) { //如果插入位置為尾部,succ則為null,原來鏈表的last設置為此刻的pred節(jié)點
succ = null;
pred = last;
} else {
succ = node(index); //否則,index所在節(jié)點設置為succ,succ的前置節(jié)點設為pred
pred = succ.prev;
}
for (Object o : a) { //循環(huán)遍歷數(shù)組a
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); //以a為element元素構(gòu)造Node節(jié)點
if (pred == null)
first = newNode;? //如果pred為空,此Node就為first節(jié)點
else
pred.next = newNode; //否則就往pred后插入該Node
pred = newNode; //newNode此刻成為新的pred, 這樣不斷循環(huán)遍歷,把這個數(shù)組插入到鏈表中
}
if (succ == null) { //如果succ為空,就把插入的最后一個節(jié)點設為last
last = pred;
} else {
pred.next = succ; //否則,把之前保存的succ接到pred后面
succ.prev = pred; //并且把succ的前向指針指向插入的最后一個元素
}
size += numNew; //記錄增長的尺寸
modCount++; //記錄修改次數(shù)
return true;
}
具體流程可以看代碼中的注釋。
刪除節(jié)點####
因為實現(xiàn)了Deque
的接口,所以還是實現(xiàn)了removeFirst
, removeLast
方法。
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
首先保存fisrt節(jié)點到f,如果為空,拋出NoSuchElementException
異常,實際還是調(diào)用unlinkFirst
完成操作。
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item; //把f.item的值保存到element
final Node<E> next = f.next; //把f.next的值記住
f.item = null;
f.next = null; // help GC //把item和next的都指向null
first = next; //next成為實際的first節(jié)點
if (next == null) //next為空的話,因為next是第一個節(jié)點,所以鏈表都是空的,last也為空
last = null;
else
next.prev = null; //next不為空,也要將它的前驅(qū)節(jié)點記為null,因為next是第一個節(jié)點
size--; //節(jié)點減少一個
modCount++; //操作次數(shù)加1
return element;
}
removeLast的實現(xiàn)基本和removeFirst對稱:
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
主要實現(xiàn)還是借助于unlinkLast
:
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item; //保存最后一個節(jié)點的值
final Node<E> prev = l.prev; //把最后一個節(jié)點的前驅(qū)節(jié)點記為prev,它將成為last節(jié)點
l.item = null;
l.prev = null; // help GC //l節(jié)點的item和prev都記為空
last = prev; //此時設置剛才記錄的前驅(qū)節(jié)點為last
if (prev == null) //prev為空的話,說明要刪除的l前面原來沒節(jié)點,那么刪了l,整個鏈表為空
first = null;
else
prev.next = null; //prev成為最后一個節(jié)點,沒有后繼節(jié)點
size--;
modCount++;
return element;
}
在指定index刪除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
首先也是檢查index是否合法,否則拋出IndexOutOfBoundsException
異常。
如果刪除成功,返回刪除的節(jié)點。
具體實現(xiàn)依賴于unlink
,也就是unlink
做實際的刪除操作:
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 = 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;
}
根據(jù)圖和代碼來看,刪除一個節(jié)點有以下幾步:
- 將刪除的節(jié)點保存在element里,同時把要刪除節(jié)點的前驅(qū)節(jié)點標記為prev,后繼節(jié)點標記為next;
- 如果prev為空,那么next節(jié)點直接為first節(jié)點,反之把prev的next指向next節(jié)點,如圖中上面彎曲的紅色箭頭所示;
- 如果next為空,那么prev節(jié)點直接為last節(jié)點,反之把next的prev指向prev節(jié)點,如圖中下面彎曲的藍色箭頭所示;
- 把要刪除的節(jié)點置空,返回第一步保存的element。
還有種刪除是以刪除的元素作為參數(shù):
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;
}
- o為null,也是遍歷鏈表,找到第一個值為null的節(jié)點,刪除;
- o部位空,遍歷鏈表,找到第一個值相等的節(jié)點,調(diào)用unlink(x)刪除。
清空列表
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++;
}
由代碼看出,也就是循環(huán)遍歷整個鏈表,將每個節(jié)點的每個屬性都置為空。
LinkedList還定義了很多別的方法,基本上和上面分析的幾個函數(shù)功能類似
- elemet和GetFirst一樣,都返回列表的頭,并且不移除它,如果列表為空,都會拋出NoSucnElement異常;
- peek也會返回第一個元素,但是為空時返回null, 不拋異常;
- remove方法內(nèi)部就是調(diào)用removeFirst,所以表現(xiàn)相同,返回移除的元素,如果列表為空,都會拋出NoSucnElement異常;
- poll也是移除第一個元素,只是會在列表為空時只返回null;
- offer和offerLast在尾部add節(jié)點, 最終調(diào)用的都是addLast方法,offerFirst在頭保護add節(jié)點,調(diào)用的就是addFirst方法;
- peekFirst返回頭節(jié)點,為空時返回null,peekLast返回尾節(jié)點,為空時返回null,都不會刪除節(jié)點;
- pollFirst刪除并返回頭節(jié)點,為空時返回null ,pollLast刪除并返回尾節(jié)點,為空時返回null;
- push和pop也是讓LinkedList具有棧的功能,也只是調(diào)用了addFirst和removeFirst函數(shù)。
ListIterator
最后重點說一下LinkedList中如何實現(xiàn)了ListIterator迭代器。
ListIterator是一個更加強大的Iterator迭代器的子類型,它只能用于各種List類的訪問。盡管Iterator只能向前移動,但是ListIterator可以雙向移動。它還可以產(chǎn)生相對于迭代器在列表中指向的當前位置的前一個和后一個元素的索引,可以用set()
方法替換它訪問過得最后一個元素。
定義如下:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
指定index,可以在一開始就獲取一個指向index位置元素的迭代器。
實際上LinkedList是實現(xiàn)了ListItr類:
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next; //用于記錄當前節(jié)點
private int nextIndex; //用于記錄當前節(jié)點所在索引
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index); //返回index處的節(jié)點,記錄為next
nextIndex = index; //記錄當前索引
}
public boolean hasNext() {
return nextIndex < size; //通過判斷nextIndex是否還在size范圍內(nèi)
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next; //記錄上一次的值
next = next.next; //往后移動一個節(jié)點
nextIndex++; //索引值也加1
return lastReturned.item; //next會返回上一次的值
}
public boolean hasPrevious() { //通過哦按段nextIndex是否還大于0,如果<=0,就證明沒有前驅(qū)節(jié)點了
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
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(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
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();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我們可以發(fā)現(xiàn)在ListIterator的操作中仍然有checkForComodification
函數(shù),而且在上面敘述的各種操作中還是會記錄modCount,所以LinkedList也是會產(chǎn)生快速失敗事件的。