引言
上一篇文章咱們一起分析了ArrayList,本篇咱們一起來看看它的姐妹LinkedList,分析的源碼是JDK8版本。
1、LinkedList結構圖
LinkedList繼承了AbstractSequentialList,實現了List、Deque、Cloneable、Serializable接口,如下圖所示:
- AbstractSequentialList類:繼承AbstractList,從該抽象類的英文註釋中可以知道,該類提供了List接口的骨幹實現,對“順序訪問”數據存儲(如鏈接列表)支持。對於隨機訪問數據(如數組),應該優先使用 AbstractList。
- Deque 接口:Deque定義了一個線性Collection,支持在兩端插入和刪除元素。
現在,我們應該知道了LinkedList是一個雙向鏈表結構,支持複製、序列化的。
2、分析源碼
我們分析的順序還是從對象的屬性,構造方法,常用方法進行分析
2.1、屬性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
//...省略部分代碼
}
上面源碼中爲LinkedList中的基本屬性,其中size爲LinkedList的長度,first爲指向頭結點,last指向尾結點,Node爲LinkedList的一個私有內部類,其定義如下,即定義了item(元素),next(指向後一個元素的指針),prev(指向前一個元素的指針)。
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中的元素爲[“A”,”B”,”C”],其內部的結構如下圖所示:
從上圖,我們可以清晰的看出LinkedList底層是雙向鏈表的實現。
2.2、構造方法
public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
源碼中只有兩個構造方法,一個是空構造方法,啥事也沒做,另外一個構造方法可以添加集合元素,轉化爲鏈表,我們可以看到方法是addAll,我們先看添加單個元素的add方法,addAll方法與其大同小異。
2.3、常用方法
2.3.1、新增
public boolean add(E e) {
linkLast(e);
return true;
}
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.記錄當前末尾節點,構造另外一個指向末尾節點的指針l
2.產生新的節點:在鏈表的末尾添加,next是爲null的
3.last指向新的節點
4.這裏有個判斷,判斷是否爲第一個元素(當l==null時,表示鏈表中是沒有節點的), 如果是第一節點,則使用first指向這個節點,若不是則當前節點的next指向新增的節點
5.size增加,操作記錄modCount增加
2.3.2、刪除
//方法1.刪除指定索引上的節點
public E remove(int index) {
//檢查索引是否正確
checkElementIndex(index);
//這裏分爲兩步,第一通過索引定位到節點,第二刪除節點
return unlink(node(index));
}
//方法2.刪除指定值的節點
public boolean remove(Object o) {
//判斷是否爲null,然後進行遍歷刪除
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;
}
通過源碼可以看出兩個方法都是通過unlink()刪除,咱們先看方法一中定位到節點的node(index)方法是如何實現的:
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的位置,是靠近first還是靠近last
- 若靠近first則從頭開始查詢,否則從尾部開始查詢,可以看出這樣避免極端情況的發生,也更好的利用了LinkedList雙向鏈表的特徵
下面再分析刪除節點中最核心的方法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;
/刪除的是第一個節點,first向後移動
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//刪除的是最後一個節點,last向前移
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
- 1.獲取到需要刪除元素當前的值,指向它前一個節點的引用,以及指向它後一個節點的引用。
- 2.判斷刪除的是否爲第一個節點,若是則first向後移動,若不是則將當前節點的前一個節點next指向當前節點的後一個節點
- 3.判斷刪除的是否爲最後一個節點,若是則last向前移動,若不是則將當前節點的後一個節點的prev指向當前節點的前一個節點
- 4.將當前節點的值置爲null
- 5.size減少並返回刪除節點的值
上面就是LinkedList添加、刪除元素的內部實現。
2.4、總結
上一篇我們分析了ArrayList,下面我們來總結對比下ArrayList和LinkedList的區別:
2.4.1、相同點
- 1.接口實現:都實現了List接口,都是線性列表的實現
- 2.線程安全:都是線程不安全的
2.4.2、區別
- 1.底層實現:ArrayList內部是數組實現,而LinkedList內部實現是雙向鏈表結構
- 2.接口實現:ArrayList實現了RandomAccess可以支持隨機元素訪問,而LinkedList實現了Deque可以當做隊列使用
- 3.性能:新增、刪除元素時ArrayList需要使用到拷貝原數組,而LinkedList只需移動指針,查找元素 ArrayList支持隨機元素訪問,而LinkedList只能一個節點接一個節點的去遍歷
結束語
寫源碼分析文章是需要耗費巨大的精力的,如果本篇文章對你有幫助,請隨手點個贊,謝謝!