Java集合LinkedList源碼剖析

Java集合LinkedList源碼剖析

白玉 IT哈哈
LinkedList也和ArrayList同樣實現了List接口,可是它執行插入和刪除操做時比ArrayList更加高效,由於它是基於鏈表的。基於鏈表也決定了它在隨機訪問方面要比ArrayList遜色一點。java

除此以外,LinkedList還提供了一些可使其做爲棧、隊列、雙端隊列的方法。這些方法中有些彼此之間只是名稱的區別,以使得這些名字在特定的上下文中顯得更加的合適。數組

先看LinkedList類的定義。ide

public class LinkedList<E>
   extends AbstractSequentialList<E>
   implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList繼承自AbstractSequenceList、實現了List及Deque接口。其實AbstractSequenceList已經實現了List接口,這裏標註出List只是更加清晰而已。AbstractSequenceList提供了List接口骨幹性的實現以減小實現List接口的複雜度。Deque接口定義了雙端隊列的操做。this

LinkedList中之定義了兩個屬性:3d

private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;

size確定就是LinkedList對象裏面存儲的元素個數了。LinkedList既然是基於鏈表實現的,那麼這個header確定就是鏈表的頭結點了,Entry就是節點對象了。一下是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;
     }
 }

只定義了存儲的元素、前一個元素、後一個元素,這就是雙向鏈表的節點的定義,每一個節點只知道本身的前一個節點和後一個節點。
來看LinkedList的構造方法。code

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)方法。blog

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)方法能夠構造鏈表並在指定位置插入節點,爲了便於理解,下面給出插入節點的示意圖。
Java集合LinkedList源碼剖析
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)

Java集合LinkedList源碼剖析

先判斷出入的數組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以後的元素不變。

爲何不直接對數組a進行操做,要將a賦值給result數組以後對result數組進行操做?
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。

Java集合LinkedList源碼剖析
Java集合LinkedList源碼剖析

Java集合LinkedList源碼剖析
Java集合LinkedList源碼剖析
下面是一個ListItr的使用實例。
Java集合LinkedList源碼剖析
結果:
Java集合LinkedList源碼剖析

LinkedList還有一個提供Iterator的方法:descendingIterator()。該方法返回一個DescendingIterator對象。DescendingIterator是LinkedList的一個內部類。

public Iterator<E> descendingIterator() {
    return new DescendingIterator();
}

下面分析詳細分析DescendingIterator類。
Java集合LinkedList源碼剖析

從類名和上面的代碼能夠看出這是一個反向的Iterator,代碼很簡單,都是調用的ListItr類中的方法。

相關文章
相關標籤/搜索