1. 程式人生 > >Java LinkedList原始碼分析

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類中的方法。

其他比較好的部落格: