LinkedList源碼分析
衆所周知,LinkedList實現了List接口,LinkedList適用於集合元素先入先出和先入後出的場景,在隊列的源碼中被頻繁使用,我們在使用一般的隊列時,就直接可以拿LinkedList來實現,如下:
LinkedList<Integer> queue = new LinkedList<>();
queue.addLast(3); //相當於入隊操作
queue.addLast(4);
queue.addLast(5);
System.out.println(queue.removeFirst()); //相當於出隊操作(poll)
System.out.println(queue.getFirst()); //相當於peek操作
可見LinkedList的操作十分的靈活!!
1. 整體架構
LinkedList的底層結構是一個雙向鏈表,如下圖:
看到上圖的結構,注意到:
- 當鏈表中沒有數據的時候,first和last是同一個節點,前後指向都爲零;
- 雙向鏈表大小隻受內存大小的限制;
1. 類定義
先來看看這個類的定義:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
注意到了沒,他還實現了Deque這個接口,所以LinkedList中還實現了poll()、peek()、push()、pop()等等,這些方法都是實現了Deque這個接口裏的抽象方法,而這些方法的實現,就是基於LikedList裏面本來的add、remove系列操作,比如看push()和pop()和peek()這三個方法:
public void push(E e) {
addFirst(e); //頭插法新增節點
}
public E pop() { //刪除頭節點
return removeFirst();
}
public E peek() { //返回頭節點
final Node<E> f = first;
return (f == null) ? null : f.item;
}
2. 底層結構
在LinedList裏面有一個爲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;
}
}
3. 常用方法源碼解析
1. 構造方法
有兩個構造方法,一個是無參構造,一個是傳入一個Collection集合,注意該集合的泛型得是當前定義的泛型的子類!
public LinkedList() {
}
//該構造函數將傳入的集合(符合要求的)轉化爲LinkedList
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
注意第二個構造函數:
- 當傳入的集合泛型不符合條件時,則會編譯出錯;
2. 新增操作
即爲雙向鏈表,所以新增節點的操作可以爲頭插,也可以是尾插;
在LikedList裏面,定義了兩個節點:
transient Node<E> first;
transient Node<E> last;
這兩個初始值都爲null;
1. add()方法(尾插)
add方法默認是從尾部開始增加節點的:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//先獲取尾節點
final Node<E> l = last;
//這裏通過構造方法就將newNode的prev設置爲last,next設置爲空
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//判斷是不是空鏈表(插入前)
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
//記錄更改
modCount++;
}
2. addFirst()方法 (頭插)
public void addFirst(E e) {
linkFirst(e);
}
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++;
}
頭插爲尾插很相似,這裏就沒有加註釋!
3. 刪除操作
同樣的,刪除也分爲頭部刪除和尾部刪除
1. remove()方法 (也就是removeFirst方法)
remove方法實現的就是刪除頭節點,而具體實現查看源碼可以發現就是調用了removeFirst:
public E remove() {
return removeFirst();
}
public E removeFirst() {
//獲取頭節點
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// 保存要刪除的頭節點的值
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
//這裏代表只有一個節點,此時應將last置空,因爲一個節點的時候first和last都是這個節點;
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
2. removeLast()方法
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
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;
}
可以看出和removeFirst的邏輯是一樣的;
3. remove(int index)方法(指定下標刪除節點)⭐
public E remove(int index) {
//檢查下標是否合法的一系列操作
checkElementIndex(index);
return unlink(node(index));
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//根據下標查找到這個節點並且返回;
Node<E> node(int index) {
//如果index小於size的一半,說明這個節點在雙向鏈表的左半側,從first向右開始查找;
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//此時index大於size的一半,說明這個節點在雙向鏈表的右半側,從last向左開始查找;
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return 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) {
//如果要刪除的節點前面爲空的話,則該節點刪除後,就應該把該節點後面的節點設置爲first;
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
//如果要刪除的節點後面爲空的話,則該節點刪除後,就應該把該節點前面的節點設置爲last;
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
注意點:
- 可以看出刪除指定下標的節點的邏輯是:
- 先通過 checkElementIndex(int index) 方法檢查要刪除的下標是否存在;
- 然後通過node(int index) 方法找到要刪除的下標那個節點;
- 執行unlink(Node node) 方法進行節點刪除;
- 結合前面的來看:
unlink代表刪除指定節點,unlinkFirst代表刪除頭節點,unlinkLast代表刪除尾節點!
- 結合前面的來看:
- 設計亮點: ⭐
- node(int index)方法在查找節點的時候並不是從頭開始遍歷,而是通過了一次二分法,來確定是從頭還是從尾開始遍歷;
4. 節點查詢
1. get(int index)方法:獲取指定下標的節點值
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
上面已經分析過node這個方法,用來獲取指定下標的節點,所以上面查詢很簡單;
2. indexOf(Object o)方法 :獲取指定節點的值的下標
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;
}
3. contains(Object o)方法:是否存在某個節點值的節點⭐
public boolean contains(Object o) {
return indexOf(o) != -1;
}
這裏直接調用了上面的方法,很巧妙!!!
4. 迭代器
1. 單項迭代器
我們知道,在JDK1.5之前Iterable接口(注意不是Iterator接口,在Iterable接口裏定義了獲取Iterator的抽象方法)中的iterator()方法是直接在Collection接口中定義的,之後Collection接口就直接實現了Iterable接口 ,自然也就繼承了iterator這個抽象方法,從而讓自己的子類去完成實現 ,所以最原始的迭代器在每個集合裏都可以直接調用iterator()方法去獲取它的迭代器 ;
LinkedList也一樣,只不過注意iterator的具體實現不是在LinkedList裏面完成的,而是它的父抽象類:AbstractSequentialList < E > ,而這個類又繼承了AbstractList 類,具體的實現就是在這個類裏面實現了迭代器的具體細節;
給上集合的一張類圖繼承關係:
所以LinkedList使用迭代器操作很簡單:
LinkedList<Integer> list = new LinkedList<>();
Iterator<Integer> integerIterator = list.iterator();
while (integerIterator.hasNext()) {
System.out.print(integerIterator.next()+" ");
}
2. 雙向迭代器 ⭐
在LinkedList中雖然沒有直接實現單向迭代器,但實現了雙向迭代器ListItr:
這個類是LinkedList的內部類,實現了ListIterator接口,而ListIterator接口實現了Iterator;
(ListIterator是專爲雙向迭代設計的一個接口,就是比Iterator多了關於從後往前遍歷的抽象方法)
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
//這裏得參數代表迭代的位置從哪開始,如果不填參數,則會調用父類AbstractList的迭代器
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();
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();
}
}
然後在LinkedList中提供了獲取這個迭代器的方法:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
3. 迭代器源碼分析⭐⭐⭐
1. 使用案例
LinkedList<Integer> list = new LinkedList<>();
list.addLast(1);
list.addLast(2);
list.addLast(3);
//使用雙向迭代器從後往前迭代,這裏參數得填,否則默認爲零,那就從0號下標往前迭代,當然沒元素;
ListIterator<Integer> listIterator = list.listIterator(2);
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous()+" ");
}
System.out.println();
//使用雙向迭代器從前往後迭代,這裏也可以傳參數,不傳默認爲零
ListIterator<Integer> listIterator1 = list.listIterator();
while (listIterator1.hasNext()) {
System.out.print(listIterator1.next()+" ");
}
輸出:
2 1
1 2 3
我們在LinkedList類中看到了自己實現的雙向迭代器類:ListItr ,這個類只有一個有參構造,需要傳入一個整數,代表開始迭代的位置,那麼上面怎麼能調用無參的listIterator()方法呢?
原因是無參的那個方法不是LinkedList實現的,是他的父類AbstractList實現的,我們來看看這個類:
我們的ArrayList和LinkedList都繼承了這個抽象類,看它的類結構,裏面封裝了兩個內部類:Itr和ListItr,看着兩個名字就知道他們是用來實現迭代器的,而ListItr又繼承了Itr,看這兩個類中的方法:
- Itr類中實現了基本的迭代,這個類實現了Iterator接口,沒有雙向迭代的功能;
- 所謂基本迭代,進去發現其實就是默認位置從下標0開始,一次往後遍歷來實現遍歷元素;
- ListItr類繼承了Itr,所以它也擁有基本的迭代功能,它也實現了ListIterator接口,所以它能實現雙向迭代功能,你看它的那幾個方法嘛,比如previous()和hasPrevious();
- ListItr構造方法也是必須要傳入參數的,用來指定你從哪開始迭代,因爲有了從後往前迭代,所以你得指定你從哪個下標開始嘛;
而在AbstractList類中,提供了iterator()和listIterator(int index)和listIterator(無參)這些方法來獲取迭代器:
-
iterator()獲取的就是上面的Itr這個類的對象;
public Iterator<E> iterator() { return new Itr(); }
- 我們在LinkedList的源碼中看到沒有覆寫iterator()這個方法,所以LinkedList獲取普通迭代器就是使用的父類Abstract類中的iterator方法來獲取的,而LinkedList實現了自己的雙向迭代器;
- 注意:ArrayList中自己即實現了自己的單向迭代器(iterator()方法),也自己實現了listIractor方法來提供自己的雙向迭代器,所以ArrayList在使用迭代器的時候使用的是自己的,不是它的父類Abstract中的;
-
listIterator(int index)獲取的就是上面ListItr類的對象:
public ListIterator<E> listIterator(final int index) { rangeCheckForAdd(index); //上面說過,這個雙向的迭代器必須傳入參數 return new ListItr(index); }
-
而listIterator(無參)就是默認參數爲零:
public ListIterator<E> listIterator() { return listIterator(0); }
這個方法正是上面那個問題的解!!LinkedList在使用listIterator卻不帶參數時,正是調用它的父類中的這個方法;
4. 總結⭐⭐
上面敘述得稍些雜亂,這裏記住這幾點:
- LinkedList只實現了自己的有參雙向迭代器(listIterator(int index)方法獲取),若要使用普通迭代器和有參雙向迭代器,都是調用父類AbstractList類的;
- 即LinkedList中只有listIterator(int index)這個方法;
- ArrayList都實現了,都是調用自己的;
- 即ArrayList中,iterator(),listIterator()、listIterator(int index)這三個方法它都有;