Java LinkedList原始碼分析
(注:本文內容基於JDK1.6)
一、概述:
LinkedList與ArrayList一樣實現List介面,只是ArrayList是List介面的大小可變陣列的實現,LinkedList是List介面連結串列的實現。基於連結串列實現的方式使得LinkedList在插入和刪除時更優於ArrayList,而隨機訪問則比ArrayList遜色些。
LinkedList實現所有可選的列表操作,並允許所有的元素包括null。
除了實現 List 介面外,LinkedList 類還為在列表的開頭及結尾 get、remove 和 insert 元素提供了統一的命名方法。這些操作允許將連結列表用作堆疊、佇列或雙端佇列。
此類實現 Deque 介面,為 add、poll 提供先進先出佇列操作,以及其他堆疊和雙端佇列操作。
所有操作都是按照雙重連結列表的需要執行的。在列表中編索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。同時,與ArrayList一樣此實現不是同步的。
二、原始碼分析:
LinkedList的定義:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
從這段程式碼中我們可以清晰地看出LinkedList繼承AbstractSequentialList,實現List、Deque、Cloneable、Serializable。其中AbstractSequentialList提供了 List 介面的骨幹實現,從而最大限度地減少了實現受“連續訪問”資料儲存(如連結列表)支援的此介面所需的工作,從而以減少實現List介面的複雜度。Deque一個線性 collection,支援在兩端插入和移除元素,定義了雙端佇列的操作。 在LinkedList中提供了兩個基本屬性size、header。
private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0;
其中size表示的LinkedList的大小,header表示連結串列的表頭,Entry為節點物件。
private static class Entry<E> {
E element; //元素節點
Entry<E> next; //下一個元素
Entry<E> previous; //上一個元素
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
上面為Entry物件的原始碼,Entry為LinkedList的內部類,它定義了儲存的元素。該元素的前一個元素、後一個元素,這是典型的雙向連結串列定義方式。
來看 LinkedList的構造方法::
public LinkedList() {
header.next = header.previous = header;
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList提供了兩個構造方法。第一個構造方法不接受引數,只是將header節點的前一節點和後一節點都設定為自身(注意,這個是一個雙向迴圈連結串列,如果不是迴圈連結串列,空連結串列的情況應該是header節點的前一節點和後一節點均為null),這樣整個連結串列其實就只有header一個節點,用於表示一個空的連結串列。第二個構造方法接收一個Collection引數c,呼叫第一個構造方法構造一個空的連結串列,之後通過addAll將c中的元素全部新增到連結串列中。來看addAll的內容。
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
// index引數指定collection中插入的第一個元素的位置
public boolean addAll(int index, Collection<? extends E> c) {
// 插入位置超過了連結串列的長度或小於0,報IndexOutOfBoundsException異常
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Object[] a = c.toArray();
int numNew = a.length;
// 若需要插入的節點個數為0則返回false,表示沒有插入元素
if (numNew==0)
return false;
modCount++;
// 儲存index處的節點。插入位置如果是size,則在頭結點前面插入,否則獲取index處的節點
Entry<E> successor = (index==size ? header : entry(index));
// 獲取前一個節點,插入時需要修改這個節點的next引用
Entry<E> predecessor = successor.previous;
// 按順序將a陣列中的第一個元素插入到index處,將之後的元素插在這個元素後面
for (int i=0; i<numNew; i++) {
// 結合Entry的構造方法,這條語句是插入操作,相當於C語言中連結串列中插入節點並修改指標
Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
// 插入節點後將前一節點的next指向當前節點,相當於修改前一節點的next指標
predecessor.next = e;
// 相當於C語言中成功插入元素後將指標向後移動一個位置以實現迴圈的功能
predecessor = e;
}
// 插入元素前index處的元素連結到插入的Collection的最後一個節點
successor.previous = predecessor;
// 修改size
size += numNew;
return true;
}
構造方法中的呼叫了addAll(Collection<? extends E> c)方法,而在addAll(Collection<? extends E> c)方法中僅僅是將size當做index引數呼叫了addAll(int index,Collection<? extends E> c)方法。
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Entry<E> e = header;
// 根據這個判斷決定從哪個方向遍歷這個連結串列
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
// 可以通過header節點向前遍歷,說明這個一個迴圈雙向連結串列,header的previous指向連結串列的最後一個節點,這也驗證了構造方法中對於header節點的前後節點均指向自己的解釋
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
結合上面程式碼中的註釋及雙向迴圈連結串列的知識,應該很容易理解LinkedList構造方法所涉及的內容。下面開始分析LinkedList的其他方法。
add(E e):
public boolean add(E e) {
addBefore(e, header);
return true;
}
從上面的程式碼可以看出,add(E e)方法只是呼叫了addBefore(E e,Entry<E> entry)方法,並且返回true。
addBefore(E e,Entry<E> entry):
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
addBefore(E e,Entry<E> entry)方法是個私有方法,所以無法在外部程式中呼叫(當然,這是一般情況,你可以通過反射上面的還是能呼叫到的)。
addBefore(E e,Entry<E> entry)先通過Entry的構造方法建立e的節點newEntry(包含了將其下一個節點設定為entry,上一個節點設定為entry.previous的操作,相當於修改newEntry的“指標”),之後修改插入位置後newEntry的前一節點的next引用和後一節點的previous引用,使連結串列節點間的引用關係保持正確。之後修改和size大小和記錄modCount,然後返回新插入的節點。
總結,addBefore(E e,Entry<E> entry)實現在entry之前插入由e構造的新節點。而add(E e)實現在header節點之前插入由e構造的新節點。
add(int index,E e):
public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
}
也是呼叫了addBefore(E e,Entry<E> entry)方法,只是entry節點由index的值決定。
構造方法,addAll(Collection<? extends E> c),add(E e),addBefor(E e,Entry<E> entry)方法可以構造連結串列並在指定位置插入節點,為了便於理解,下面給出插入節點的示意圖。
addFirst(E e):
public void addFirst(E e) {
addBefore(e, header.next);
}
addLast(E e):
public void addLast(E e) {
addBefore(e, header);
}
看上面的示意圖,結合addBefore(E e,Entry<E> entry)方法,很容易理解addFrist(E e)只需實現在header元素的下一個元素之前插入,即示意圖中的一號之前。addLast(E e)只需在實現在header節點前(因為是迴圈連結串列,所以header的前一個節點就是連結串列的最後一個節點)插入節點(插入後在2號節點之後)。
clear():
public void clear() {
Entry<E> e = header.next;
// e可以理解為一個移動的“指標”,因為是迴圈連結串列,所以回到header的時候說明已經沒有節點了
while (e != header) {
// 保留e的下一個節點的引用
Entry<E> next = e.next;
// 接觸節點e對前後節點的引用
e.next = e.previous = null;
// 將節點e的內容置空
e.element = null;
// 將e移動到下一個節點
e = next;
}
// 將header構造成一個迴圈連結串列,同構造方法構造一個空的LinkedList
header.next = header.previous = header;
// 修改size
size = 0;
modCount++;
}
上面程式碼中的註釋已經足以解釋這段程式碼的邏輯,需要注意的是提到的“指標”僅僅是概念上的類比,Java並不存在“指標”的概念,而只有引用,為了便於理解所以部分說明使用了“指標”。
contains(Object o):
public boolean contains(Object o) {
return indexOf(o) != -1;
}
僅僅只是判斷o在連結串列中的索引。先看indexOf(Object o)方法。
public int indexOf(Object o) {
int index = 0;
if (o==null) {
for (Entry e = header.next; e != header; e = e.next) {
if (e.element==null)
return index;
index++;
}
} else {
for (Entry e = header.next; e != header; e = e.next) {
if (o.equals(e.element))
return index;
index++;
}
}
return -1;
}
indexOf(Object o)判斷o連結串列中是否存在節點的element和o相等,若相等則返回該節點在連結串列中的索引位置,若不存在則放回-1。
contains(Object o)方法通過判斷indexOf(Object o)方法返回的值是否是-1來判斷連結串列中是否包含物件o。
element():
public E element() {
return getFirst();
}
getFirst():
public E getFirst() {
if (size==0)
throw new NoSuchElementException();
return header.next.element;
}
element()方法呼叫了getFirst()返回連結串列的第一個節點的元素。為什麼要提供功能一樣的兩個方法,像是包裝了一下名字?其實這只是為了在不同的上下文“語境”中能通過更貼切的方法名呼叫罷了。
get(int index):
public E get(int index) {
return entry(index).element;
}
get(int index)方法用於獲得指定索引位置的節點的元素。它通過entry(int index)方法獲取節點。entry(int index)方法遍歷連結串列並獲取節點,在上面有說明過,不再陳述。
set(int index,E element):
public E set(int index, E element) {
Entry<E> e = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}
先獲取指定索引的節點,之後保留原來的元素,然後用element進行替換,之後返回原來的元素。
getLast():
public E getLast() {
if (size==0)
throw new NoSuchElementException();
return header.previous.element;
}
getLast()方法和getFirst()方法類似,只是獲取的是header節點的前一個節點的元素。因為是迴圈連結串列,所以header節點的前一節點就是連結串列的最後一個節點。
lastIndexOf(Object o):
public int lastIndexOf(Object o) {
int index = size;
if (o==null) {
for (Entry e = header.previous; e != header; e = e.previous) {
index--;
if (e.element==null)
return index;
}
} else {
for (Entry e = header.previous; e != header; e = e.previous) {
index--;
if (o.equals(e.element))
return index;
}
}
return -1;
}
因為查詢的是last index,即最後一次出現的位置,所以採用由後向前的遍歷方式。因為採用了有後向前的遍歷,所以index被賦值為size,並且迴圈體內執行時都進行減操作。分兩種情況判斷是否存在,分別是null和不為空。
offer(E e):
public boolean offer(E e) {
return add(e);
}
在連結串列尾部插入元素。
offerFirst(E e):
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
在連結串列開頭插入元素。
offerLast(E e):
public boolean offerLast(E e) {
addLast(e);
return true;
}
在連結串列末尾插入元素。
上面這三個方法都只是呼叫了相應的add方法,同樣只是提供了不同的方法名在不同的語境下使用。
peek():
public E peek() {
if (size==0)
return null;
return getFirst();
}
peekFirst():
public E peekFirst() {
if (size==0)
return null;
return getFirst();
}
peekLast():
public E peekLast() {
if (size==0)
return null;
return getLast();
}
上面的三個方法也很簡單,只是呼叫了對應的get方法。
poll():
public E poll() {
if (size==0)
return null;
return removeFirst();
}
pollFirst():
public E pollFirst() {
if (size==0)
return null;
return removeFirst();
}
pollLast():
public E pollLast() {
if (size==0)
return null;
return removeLast();
}
poll相關的方法都是獲取並移除某個元素。都是和remove操作相關。
pop():
public E pop() {
return removeFirst();
}
push(E e):
public void push(E e) {
addFirst(e);
}
這兩個方法對應棧的操作,即彈出一個元素和壓入一個元素,僅僅是呼叫了removeFirst()和addFirst()方法。
下面集中看remove相關操作的方法。
remove():
public E remove() {
return removeFirst();
}
remove(int index):
public E remove(int index) {
return remove(entry(index));
}
remove(Object o):
public boolean remove(Object o) {
if (o==null) {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (e.element==null) {
remove(e);
return true;
}
}
} else {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
removeFirst():
public E removeFirst() {
return remove(header.next);
}
removeLast():
public E removeLast() {
return remove(header.previous);
}
removeFirstOccurrence():
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
removeLastOccurence():
public boolean removeLastOccurrence(Object o) {
if (o==null) {
for (Entry<E> e = header.previous; e != header; e = e.previous) {
if (e.element==null) {
remove(e);
return true;
}
}
} else {
for (Entry<E> e = header.previous; e != header; e = e.previous) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
幾個remove方法最終都是呼叫了一個私有方法:remove(Entry<E> e),只是其他簡單邏輯上的區別。下面分析remove(Entry<E> e)方法。
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
// 保留將被移除的節點e的內容
E result = e.element;
// 將前一節點的next引用賦值為e的下一節點
e.previous.next = e.next;
// 將e的下一節點的previous賦值為e的上一節點
e.next.previous = e.previous;
// 上面兩條語句的執行已經導致了無法在連結串列中訪問到e節點,而下面解除了e節點對前後節點的引用
e.next = e.previous = null;
// 將被移除的節點的內容設為null
e.element = null;
// 修改size大小
size--;
modCount++;
// 返回移除節點e的內容
return result;
}
clone():
public Object clone() {
LinkedList<E> clone = null;
try {
clone = (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
clone.header = new Entry<E>(null, null, null);
clone.header.next = clone.header.previous = clone.header;
clone.size = 0;
clone.modCount = 0;
for (Entry<E> e = header.next; e != header; e = e.next)
clone.add(e.element);
return clone;
}
呼叫父類的clone()方法初始化物件連結串列clone,將clone構造成一個空的雙向迴圈連結串列,之後將header的下一個節點開始將逐個節點新增到clone中。最後返回克隆的clone物件。
toArray():
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Entry<E> e = header.next; e != header; e = e.next)
result[i++] = e.element;
return result;
}
建立大小和LinkedList相等的陣列result,遍歷連結串列,將每個節點的元素element複製到陣列中,返回陣列。
toArray(T[] a):
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Entry<E> e = header.next; e != header; e = e.next)
result[i++] = e.element;
if (a.length > size)
a[size] = null;
return a;
}
先判斷出入的陣列a的大小是否足夠,若大小不夠則拓展。這裡用到了發射的方法,重新例項化了一個大小為size的陣列。之後將陣列a賦值給陣列result,遍歷連結串列向result中新增的元素。最後判斷陣列a的長度是否大於size,若大於則將size位置的內容設定為null。返回a。
從程式碼中可以看出,陣列a的length小於等於size時,a中所有元素被覆蓋,被拓展來的空間儲存的內容都是null;若陣列a的length的length大於size,則0至size-1位置的內容被覆蓋,size位置的元素被設定為null,size之後的元素不變。
LinkedList的Iterator:
除了Entry,LinkedList還有一個內部類:ListItr。ListItr實現了ListIterator介面,可知它是一個迭代器,通過它可以遍歷修改LinkedList。在LinkedList中提供了獲取ListItr物件的方法:listIterator(int index)。
public ListIterator<E> listIterator(int index) {
return new ListItr(index);
}
該方法只是簡單的返回了一個ListItr物件。
LinkedList中還有通過整合獲得的listIterator()方法,該方法只是呼叫了listIterator(int index)並且傳入0。
下面詳細分析ListItr。
private class ListItr implements ListIterator<E> {
// 最近一次返回的節點,也是當前持有的節點
private Entry<E> lastReturned = header;
// 對下一個元素的引用
private Entry<E> next;
// 下一個節點的index
private int nextIndex;
private int expectedModCount = modCount;
// 構造方法,接收一個index引數,返回一個ListItr物件
ListItr(int index) {
// 如果index小於0或大於size,丟擲IndexOutOfBoundsException異常
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
// 判斷遍歷方向
if (index < (size >> 1)) {
// next賦值為第一個節點
next = header.next;
// 獲取指定位置的節點
for (nextIndex=0; nextIndex<index; nextIndex++)
next = next.next;
} else {
// else中的處理和if塊中的處理一致,只是遍歷方向不同
next = header;
for (nextIndex=size; nextIndex>index; nextIndex--)
next = next.previous;
}
}
// 根據nextIndex是否等於size判斷時候還有下一個節點(也可以理解為是否遍歷完了LinkedList)
public boolean hasNext() {
return nextIndex != size;
}
// 獲取下一個元素
public E next() {
checkForComodification();
// 如果nextIndex==size,則已經遍歷完連結串列,即沒有下一個節點了(實際上是有的,因為是迴圈連結串列,任何一個節點都會有上一個和下一個節點,這裡的沒有下一個節點只是說所有節點都已經遍歷完了)
if (nextIndex == size)
throw new NoSuchElementException();
// 設定最近一次返回的節點為next節點
lastReturned = next;
// 將next“向後移動一位”
next = next.next;
// index計數加1
nextIndex++;
// 返回lastReturned的元素
return lastReturned.element;
}
public boolean hasPrevious() {
return nextIndex != 0;
}
// 返回上一個節點,和next()方法相似
public E previous() {
if (nextIndex == 0)
throw new NoSuchElementException();
lastReturned = next = next.previous;
nextIndex--;
checkForComodification();
return lastReturned.element;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex-1;
}
// 移除當前Iterator持有的節點
public void remove() {
checkForComodification();
Entry<E> lastNext = lastReturned.next;
try {
LinkedList.this.remove(lastReturned);
} catch (NoSuchElementException e) {
throw new IllegalStateException();
}
if (next==lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = header;
expectedModCount++;
}
// 修改當前節點的內容
public void set(E e) {
if (lastReturned == header)
throw new IllegalStateException();
checkForComodification();
lastReturned.element = e;
}
// 在當前持有節點後面插入新節點
public void add(E e) {
checkForComodification();
// 將最近一次返回節點修改為header
lastReturned = header;
addBefore(e, next);
nextIndex++;
expectedModCount++;
}
// 判斷expectedModCount和modCount是否一致,以確保通過ListItr的修改操作正確的反映在LinkedList中
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
下面是一個ListItr的使用例項。
LinkedList<String> list = new LinkedList<String>();
list.add("First");
list.add("Second");
list.add("Thrid");
System.out.println(list);
ListIterator<String> itr = list.listIterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
try {
System.out.println(itr.next());// throw Exception
} catch (Exception e) {
// TODO: handle exception
}
itr = list.listIterator();
System.out.println(list);
System.out.println(itr.next());
itr.add("new node1");
System.out.println(list);
itr.add("new node2");
System.out.println(list);
System.out.println(itr.next());
itr.set("modify node");
System.out.println(list);
itr.remove();
System.out.println(list);
執行結果:
[First, Second, Thrid]
First
Second
Thrid
[First, Second, Thrid]
First
[First, new node1, Second, Thrid]
[First, new node1, new node2, Second, Thrid]
Second
[First, new node1, new node2, modify node, Thrid]
[First, new node1, new node2, Thrid]
LinkedList還有一個提供Iterator的方法:descendingIterator()。該方法返回一個DescendingIterator物件。DescendingIterator是LinkedList的一個內部類。
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
下面分析詳細分析DescendingIterator類。
private class DescendingIterator implements Iterator {
// 獲取ListItr物件
final ListItr itr = new ListItr(size());
// hasNext其實是呼叫了itr的hasPrevious方法
public boolean hasNext() {
return itr.hasPrevious();
}
// next()其實是呼叫了itr的previous方法
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
從類名和上面的程式碼可以看出這是一個反向的Iterator,程式碼很簡單,都是呼叫的ListItr類中的方法。
其他比較好的部落格: