JAVA常用集合原始碼分析:LinkedList
概述
上一篇我們介紹了ArrayList,我們知道它的底層是基於陣列實現的,提到陣列,我們就馬上會想到它的兄弟連結串列,今天我們要介紹的LinkedList就是基於連結串列實現的。
繼承結構
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
List介面:列表,add、set、等一些對列表進行操作的方法
Deque介面:有佇列的各種特性,可以當佇列用
Cloneable介面:能夠複製,使用那個copy方法。
Serializable介面:能夠序列化。
注意:並沒有實現RandomAccess:那麼就推薦使用iterator,在其中就有一個foreach,增強的for迴圈,其中原理也就是iterator,我們在使用的時候,使用foreach或者iterator都可以。
原始碼分析
先看有哪些成員變數
//元素個數
transient int size = 0;
//首節點
transient Node<E> first;
//尾節點
transient Node<E> last;
在這裡,我們發現first和last都是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; } }
根據原始碼,我們可以發現節點有三個屬性,分別是值和指向一前一後的兩個指標
看到這裡,我們可以得出結論,LinkedList是底層是一個雙向連結串列,但究竟是不是雙向迴圈連結串列呢?我們還得繼續往下看。
有兩個構造方法
//空實現
public LinkedList() {
}
//用一個集合構建
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
第二個構造方法中,出現了addAll(e)方法,顯然該方法實現了把c中所以元素加到一個空連結串列中,我們來看看它的具體實現
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
只有短短兩行,主要呼叫了我們的addAll(int, E)方法,繼續看它的實現
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //檢查邊界
Object[] a = c.toArray(); // 把c裡的值轉化為陣列
int numNew = a.length;
if (numNew == 0) //如果c中無元素,則增加失敗
return false;
Node<E> pred, succ; // 兩個指標,一個指前,一個後
if (index == size) { //注意我們建構函式中一開始傳進來的就是size,所以會進入
succ = null;
pred = last;
} else { //當我們傳入的不是size的時候,進入這裡
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;
}
程式碼有點長,但是不難,容易看懂,同時在這個方法中,我們也可以知道底層只是個雙向連結串列,並非是雙向迴圈連結串列啦,事實上JDK1.7之前一直是迴圈連結串列來著,至於為啥要改,我也不敢妄下定論....
好啦,分析完了建構函式,我們接著看他一些常用的方法的實現把
常用方法
1.add(E)
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++;
}
哈哈,程式碼不難,一看就懂
2.add(int , e) 在指定位置增加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) //如果剛好是在最後一個增加,就直接加啦
linkLast(element);
else
linkBefore(element, node(index)); //在中間加
}
//在中間加的時候利用一分為二思想,看index離頭近還是離尾近
Node<E> node(int index) {
if (index < (size >> 1)) { //index<size/2 如果離頭近,則從前往後找
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;
}
}
具體的思路我都寫在註釋上啦,在node(index)的時候,運用了一分為二思想,結合了雙向連結串列的優點,有點巧妙
3.remove(Object) 刪除第一次的Object
其實思路不難,就是先找到出現的位置,然後執行刪除操作
public boolean remove(Object o) {
if (o == null) { // 區分是否為空,因為空值無法執行 equals方法
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了
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就行啦
first = next;
} else { //前繼指向它的後繼,相當於跳過它啦
prev.next = next;
x.prev = null; //置空
}
if (next == null) {
last = prev; // 太簡單,跳過
} else {
next.prev = prev; //next的前繼指向 原節點的前繼。
x.next = null;
}
x.item = null; //置空,讓GC回收它
size--;
modCount++;
return element;
}
可以結合上面的圖來理解
4.remove(index) 刪除給定位置的物件啦
其實原理都差不多,首先是定位,再刪除...注意刪除的點是頭或者尾節點這種特殊情況就行啦
5.get(index)
public E get(int index) {
checkElementIndex(index); //檢查邊界
return node(index).item;
}
核心定位程式碼node(index)之前已經介紹過啦,雖然進行了一些優化,效能已經變為了O(n/2),但還是非常低效的。
總結
- LinkedList 插入,刪除都是移動指標效率很高。
- 查詢需要進行遍歷查詢,效率較低
與ArrayList的比較
- ArrayList 基於動態陣列實現,LinkedList 基於雙向連結串列實現;
- ArrayList 支援隨機訪問,LinkedList 不支援;
- LinkedList 在任意位置新增刪除元素更快。
想說的話
哈哈第二次看原始碼啦,雖然速度慢,但感覺還是不錯的啦,分析原始碼的思路感覺更加順暢了些。有些觀點參考了其他的部落格,在瀏覽其他部落格的時候,也有發現其他部落格說的不夠準確的地方,也正是這樣,讓我感覺部落格必須要嚴謹些,一定要儘量多瞭解些,不然對其他初學者產生勿擾就糟糕啦哈哈哈哈