LinkedList源碼解析

概要


   前面,咱們已經學習了ArrayList。這一章咱們接着學習List的實現類——LinkedList。和學習ArrayList同樣,接下來呢,咱們先對LinkedList有個總體認識,而後再學習它的源碼;最後再經過實例來學會使用LinkedList。內容包括html

  一、LinkedList介紹java

 

一、LinkedList介紹

  LinkedList 類的集成關係:node

1 public class LinkedList<E>
2     extends AbstractSequentialList<E>
3     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
4 {

  由此可知:數組

  LinkedList 是一個繼承於AbstractSequentialList的雙向鏈表。它也能夠被看成堆棧、隊列或雙端隊列進行操做。
  LinkedList 實現 List 接口,能對它進行隊列操做。
  LinkedList 實現 Deque 接口,即能將LinkedList看成雙端隊列使用。
  LinkedList 實現了Cloneable接口,即覆蓋了函數clone(),能克隆。
  LinkedList 實現java.io.Serializable接口,這意味着LinkedList支持序列化,能經過序列化去傳輸。
  LinkedList 是非同步的。數據結構

  既然LinkedList是基於鏈表結構的一種List,在分析LinkedList源碼前有必要對鏈表結構進行說明。函數

鏈表結構說明:

  簡單來講,連接就是不使用數組,將元素想項鍊同樣串起來。每個元素包含集合中下一個元素的引用變量源碼分析

  1.1.單向鏈表
          單向鏈表就是經過每一個結點的指針指向下一個結點從而連接起來的結構,最後一個節點的next指向null。
 
          
 
     1. 2.單向循環鏈表
          單向循環鏈表和單向列表的不一樣是,最後一個節點的next不是指向null,而是指向head節點,造成一個「環」。
 
          
 
     1. 3.雙向鏈表
          從名字就能夠看出,雙向鏈表是包含兩個指針的,pre指向前一個節點,next指向後一個節點,可是第一個節點head的pre指向null,最後一個節點的tail指向null。
 
          
 
     1. 4.雙向循環鏈表
          雙向循環鏈表和雙向鏈表的不一樣在於,第一個節點的pre指向最後一個節點,最後一個節點的next指向第一個節點,也造成一個「環」。 而LinkedList就是基於雙向循環鏈表設計的。
 
          
 
         更形象的解釋下就是:雙向循環鏈表就像一羣小孩手牽手圍成一個圈,第一個小孩的右手拉着第二個小孩的左手,第二個小孩的左手拉着第一個小孩的右手。。。最後一個小孩的右手拉着第一個小孩的左手。
 
  ok,鏈表的概念介紹完了,下面進入寫註釋和源碼分析部分,可是在這以前仍是要提醒一句,不是囉嗦哦,鏈表操做理解起來比數組困難了很多,因此務必要理解上面的圖解,若是源碼解析過程當中遇到理解困難,請返回來照圖理解

二、LinkedList數據結構

  LinkedList一共有三個成員變量,分別爲。學習

1      transient int size = 0;
2 
3     transient Node<E> first;
4 
5     transient Node<E> last;

  size和ArrayList同樣,用來記錄集合所包含的元素數量。first用來記錄集合的第一個元素,last用來記錄集合的最後一個元素。而咱們能夠看到,first和last的數據類型都是Node<E>,Node是LinkedList的一個內部類,其代碼以下:測試

 1   private static class Node<E> {
 2         E item;
 3         Node<E> next;
 4         Node<E> prev;
 5 
 6         Node(Node<E> prev, E element, Node<E> next) {
 7             this.item = element;
 8             this.next = next;
 9             this.prev = prev;
10         }
11     }

  而內部類Node中的item元素,就是用來記錄集合的元素,next用來記錄下一個元素,prev用來記錄上一個元素。ui

因而可知:LinkedList是一個雙向鏈表。

三、LinkedList源碼方法解析

  3.1構造器方法

    LinkedList一個有兩個構造器方法,分別爲:

  

 1 /**
 2      * 建立一個空的集合
 3      */
 4     public LinkedList() {
 5     }
 6 /**
 7     * 先建立一個空的集合,而後將c中的元素所有添加到集合中
 8      */
 9     public LinkedList(Collection<? extends E> c) {
10         this();
11         addAll(c);
12     }

  兩個構造方法都很簡單,這裏就不囉嗦了。而addAll如何實現,咱們在後面會講到。

  3.2LinkedLisk的關鍵私有方法

  在講LinkedList的增刪改查以前,咱們先講幾個LinkedLisk的私有方法。

  

 1  private void linkFirst(E e) {
 2         final Node<E> f = first;
 3         final Node<E> newNode = new Node<>(null, e, f);
 4         first = newNode;
 5         if (f == null)
 6             last = newNode;
 7         else
 8             f.prev = newNode;
 9         size++;
10         modCount++;
11     }

  該私有方法用來將元素e放置在鏈表的第一位。先用變量f提取原鏈表的第一個元素first,若是f爲空,則原鏈表爲空鏈表,那麼新鏈表就只有一個元素e。也就是新鏈表的first和last都爲元素e。若是f不爲空,則新鏈表的first=e,f。prev = newNode。今後方法,就能夠看出,LinkedList爲雙向不循環鏈表。


 

 1    void linkLast(E e) {
 2         final Node<E> l = last;
 3         final Node<E> newNode = new Node<>(l, e, null);
 4         last = newNode;
 5         if (l == null)
 6             first = newNode;
 7         else
 8             l.next = newNode;
 9         size++;
10         modCount++;
11     }

  該私有方法用來將元素e放置在鏈表的最後一位。處理思想與linkFirst相同。


 1  void linkBefore(E e, Node<E> succ) {
 2         // assert succ != null;
 3         final Node<E> pred = succ.prev;
 4         final Node<E> newNode = new Node<>(pred, e, succ);
 5         succ.prev = newNode;
 6         if (pred == null)
 7             first = newNode;
 8         else
 9             pred.next = newNode;
10         size++;
11         modCount++;
12     }

    該方法用來將元素e放置在節點succ以前。處理思想:先提取succ.prev元素,而後將pred設爲secc.prev,若是pred爲空,則first=newNode,不然pred.next=newNode,也就是將新元素e放置在pred和secc之間。


 1  private E unlinkFirst(Node<E> f) {
 2         // assert f == first && f != null;
 3         final E element = f.item;
 4         final Node<E> next = f.next;
 5         f.item = null;
 6         f.next = null; // help GC
 7         first = next;
 8         if (next == null)
 9             last = null;
10         else
11             next.prev = null;
12         size--;
13         modCount++;
14         return element;
15     }
 1     private E unlinkLast(Node<E> l) {
 2         // assert l == last && l != null;
 3         final E element = l.item;
 4         final Node<E> prev = l.prev;
 5         l.item = null;
 6         l.prev = null; // help GC
 7         last = prev;
 8         if (prev == null)
 9             first = null;
10         else
11             prev.next = null;
12         size--;
13         modCount++;
14         return element;
15     }

這兩個方法分別用來刪除第一個元素和最後一個元素。

 1 E unlink(Node<E> x) {
 2         // assert x != null;
 3         final E element = x.item;
 4         final Node<E> next = x.next;
 5         final Node<E> prev = x.prev;
 6 
 7         if (prev == null) {
 8             first = next;
 9         } else {
10             prev.next = next;
11             x.prev = null;
12         }
13 
14         if (next == null) {
15             last = prev;
16         } else {
17             next.prev = prev;
18             x.next = null;
19         }
20 
21         x.item = null;
22         size--;
23         modCount++;
24         return element;
25     }

該方法用來刪除一個非空元素。

 1  Node<E> node(int index) {
 2         // assert isElementIndex(index);
 3 
 4         if (index < (size >> 1)) {
 5             Node<E> x = first;
 6             for (int i = 0; i < index; i++)
 7                 x = x.next;
 8             return x;
 9         } else {
10             Node<E> x = last;
11             for (int i = size - 1; i > index; i--)
12                 x = x.prev;
13             return x;
14         }
15     }

  該方法用來獲取下標爲index的節點。由方法能夠看出當index小於size/2時,從first開始遍歷,不然從last開始遍歷。

也就是當index越靠近size/2的位置時,在獲取節點時須要遍歷的次數越多。


  3.3LinkedList增長元素的方法

   1 public void addFirst(E e) { 2 linkFirst(e); 3 } 

   1 public void addLast(E e) { 2 linkLast(e); 3 } 

這兩個方法分別調用linkFirst(e)和linkeLast(e)來在第一位和最後一位增長元素。

1    public boolean add(E e) {
2         linkLast(e);
3         return true;
4     }

由此方法能夠看出,linkedList增長元素默認在鏈表的最後一位增長元素。


 

 1   public boolean addAll(Collection<? extends E> c) {
 2         return addAll(size, c);
 3     }
 4  public boolean addAll(int index, Collection<? extends E> c) {
 5         checkPositionIndex(index);
 6 
 7         Object[] a = c.toArray();
 8         int numNew = a.length;
 9         if (numNew == 0)
10             return false;
11 
12         Node<E> pred, succ;
13         if (index == size) {
14             succ = null;
15             pred = last;
16         } else {
17             succ = node(index);
18             pred = succ.prev;
19         }
20 
21         for (Object o : a) {
22             @SuppressWarnings("unchecked") E e = (E) o;
23             Node<E> newNode = new Node<>(pred, e, null);
24             if (pred == null)
25                 first = newNode;
26             else
27                 pred.next = newNode;
28             pred = newNode;
29         }
30 
31         if (succ == null) {
32             last = pred;
33         } else {
34             pred.next = succ;
35             succ.prev = pred;
36         }
37 
38         size += numNew;
39         modCount++;
40         return true;
41     }

  能夠看出,該方法就是循環遍歷集合c,而後依次插入到鏈表的最後。而前面的構造器LinkedList(Collection<? extends E> c),所調用的addAll(c)方法,實際上也是調用的addAll(size, c)方法,而當時size等於0,則也就是從元素的第一位依次遍歷集合c插入到鏈表中。


1     public void add(int index, E element) {
2         checkPositionIndex(index);
3 
4         if (index == size)
5             linkLast(element);
6         else
7             linkBefore(element, node(index));
8     }

  該方法用來在插入元素到鏈表的固定位置。若是index == size,則直接在鏈表的最後插入元素,不然調用linkBefore(E e,Node node),在節點以前插入元素。


 1   public boolean offer(E e) {
 2         return add(e);
 3     }
 4    public boolean offerFirst(E e) {
 5         addFirst(e);
 6         return true;
 7     }
 8     public boolean offerLast(E e) {
 9         addLast(e);
10         return true;
11     }
12     public void push(E e) {
13         addFirst(e);
14     }

這四個方法都是由接口Deque所提供的,其實現直接調用了原有的add方法和addFirst、addList方法。


  3.4LinkedList刪除元素的方法

 1    public E removeFirst() {
 2         final Node<E> f = first;
 3         if (f == null)
 4             throw new NoSuchElementException();
 5         return unlinkFirst(f);
 6     }
 7  public E removeLast() {
 8         final Node<E> l = last;
 9         if (l == null)
10             throw new NoSuchElementException();
11         return unlinkLast(l);
12     }

  兩個方法分別用來刪除第一個和最後一個元素。分別調用了私有方法unlinkFirst(f) 和unlinkLast(l)來處理刪除元素。


 1  public boolean remove(Object o) {
 2         if (o == null) {
 3             for (Node<E> x = first; x != null; x = x.next) {
 4                 if (x.item == null) {
 5                     unlink(x);
 6                     return true;
 7                 }
 8             }
 9         } else {
10             for (Node<E> x = first; x != null; x = x.next) {
11                 if (o.equals(x.item)) {
12                     unlink(x);
13                     return true;
14                 }
15             }
16         }
17         return false;
18     }

  該方法用來刪除元素o。從first開始遍歷鏈表,若是有第一個等於o的元素,則刪除並返回true。


 1     public void clear() {
 2         // Clearing all of the links between nodes is "unnecessary", but:
 3         // - helps a generational GC if the discarded nodes inhabit
 4         //   more than one generation
 5         // - is sure to free memory even if there is a reachable Iterator
 6         for (Node<E> x = first; x != null; ) {
 7             Node<E> next = x.next;
 8             x.item = null;
 9             x.next = null;
10             x.prev = null;
11             x = next;
12         }
13         first = last = null;
14         size = 0;
15         modCount++;
16     }

  該方法用來刪除鏈表的全部元素。從first開始遍歷鏈表,將全部節點都設置爲null。其實本能夠直接將first和last設置爲null就能夠了,可是爲了垃圾回收機制,仍是將全部的節點都清空了。


1     public E remove(int index) {
2         checkElementIndex(index);
3         return unlink(node(index));
4     }

該方法用來刪除下標爲index的節點元素。其處理方法時先用node(index)獲取該下標的節點,而後調用私有方法unlink(Node node)刪除該節點。


    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
1    public E pollFirst() {
2         final Node<E> f = first;
3         return (f == null) ? null : unlinkFirst(f);
4     }
1 public E pollLast() {
2         final Node<E> l = last;
3         return (l == null) ? null : unlinkLast(l);
4     }

這三方法分別用來刪除並返回第一個元素和刪除並返回最後一個元素。


  public E remove() {
        return removeFirst();
    }

  該方法用來刪除第一個元素,由此方法能夠看出,LinkedList是默認從第一個元素開始刪除的。由此能夠看出LinkedList是符合隊列的先進先出原則的。


 

 1 public E pop() { 2 return removeFirst(); 3 } 

該方法用來刪除並返回最後一個元素。


  public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

  該方法用來刪除第一次出現的o元素。


 1  public boolean removeLastOccurrence(Object o) {
 2         if (o == null) {
 3             for (Node<E> x = last; x != null; x = x.prev) {
 4                 if (x.item == null) {
 5                     unlink(x);
 6                     return true;
 7                 }
 8             }
 9         } else {
10             for (Node<E> x = last; x != null; x = x.prev) {
11                 if (o.equals(x.item)) {
12                     unlink(x);
13                     return true;
14                 }
15             }
16         }
17         return false;
18     }

  該方法用來刪除最後一次出現的o元素。


  3.5LinkedList的修改元素方法

1    public E set(int index, E element) {
2         checkElementIndex(index);
3         Node<E> x = node(index);
4         E oldVal = x.item;
5         x.item = element;
6         return oldVal;
7     }

  LinkedList用來修改元素的方法只有一個,set(int index, E element),設置下標爲index的元素爲element.處理邏輯爲先提取原位置的元素x,而後用新元素element替換舊元素。


 

  3.6 LinkedList的獲取元素方法

    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
   public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
 public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
   public E element() {
        return getFirst();
    }
   public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

 這五個方法處理邏輯都很是簡單,分別是獲取第一個或者獲取最後一個元素。都是直接獲取鏈表第一個或者最後一個節點,而後獲取節點中的元素。


  public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }
 public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

public int lastIndexOf(Object o) {
  int index = size;
  if (o == null) {
    for (Node<E> x = last; x != null; x = x.prev) {
    index--;
    if (x.item == null)
    return index;
    }
  } else {
    for (Node<E> x = last; x != null; x = x.prev) {
    index--;
    if (o.equals(x.item))
    return index;
    }
  }
  return -1;
}

 

這三個方法時用來查詢元素中是否含有元素o。都是經過遍歷的方式,來查找鏈表中是否有元素o。indexOf方法,若是含有元素o返回o的下標,contains方法則是若是含有元素o返回true。lastIndexOf用來返回鏈表中元素最後出現的位置的下標。


 1 public int size() { 2 return size; 3 } 

該方法用來查看鏈表中元素的個數。


1     public E get(int index) {
2         checkElementIndex(index);
3         return node(index).item;
4     }

該方法用來返回下標爲index的元素。處理邏輯是先經過下標獲取該下標的節點,而後獲取節點中的元素。


  3.7LinkedList的其餘經常使用方法

  

 1     public Object clone() {
 2         LinkedList<E> clone = superClone();
 3 
 4         // Put clone into "virgin" state
 5         clone.first = clone.last = null;
 6         clone.size = 0;
 7         clone.modCount = 0;
 8 
 9         // Initialize clone with our elements
10         for (Node<E> x = first; x != null; x = x.next)
11             clone.add(x.item);
12 
13         return clone;
14     }

 該方法用來複制鏈表。經過遍歷鏈表中的節點,而後一次將節點中的元素加入到新的鏈表中。由方法能夠看出,新鏈表中的元素爲就鏈表中元素的引用,並無將元素也進行復制,因此LinkedList的clone方法也爲潛複製。


LinkedList的迭代器

  LinkedList的iterator方法由其集成的父類AbstractSequentialList實現,其代碼以下。

1     public Iterator<E> iterator() {
2         return listIterator();
3     }
4  public abstract ListIterator<E> listIterator(int index);

  而LinkedList實現了listIterator方法,並有一個內部類實現了ListIterator<E>接口。

  1 private class ListItr implements ListIterator<E> {
  2         //最近一次返回的節點,也就是當前的節點
  3         private Node<E> lastReturned;
  4         //下一個節點
  5         private Node<E> next;
  6         //下一個節點的下標
  7         private int nextIndex;
  8         private int expectedModCount = modCount;
  9         // 構造方法,接收一個index參數,返回一個ListItr對象
 10         ListItr(int index) {
 11             // assert isPositionIndex(index);
 12             next = (index == size) ? null : node(index);
 13             nextIndex = index;
 14         }
 15         // 根據nextIndex是否等於size判斷時候還有下一個節點(也能夠理解爲是否遍歷完了LinkedList)
 16         public boolean hasNext() {
 17             return nextIndex < size;
 18         }
 19         //獲取下一個元素
 20         public E next() {
 21             checkForComodification();
 22             if (!hasNext())
 23                 throw new NoSuchElementException();
 24 
 25             lastReturned = next;
 26             next = next.next;
 27             nextIndex++;
 28             return lastReturned.item;
 29         }
 30         //根據nextIndex判斷是否有上一個節點
 31         public boolean hasPrevious() {
 32             return nextIndex > 0;
 33         }
 34         //獲取上一個節點的元素
 35         public E previous() {
 36             checkForComodification();
 37             if (!hasPrevious())
 38                 throw new NoSuchElementException();
 39 
 40             lastReturned = next = (next == null) ? last : next.prev;
 41             nextIndex--;
 42             return lastReturned.item;
 43         }
 44         //獲取當前節點的下標
 45         public int nextIndex() {
 46             return nextIndex;
 47         }
 48         //獲取上一個節點的座標
 49         public int previousIndex() {
 50             return nextIndex - 1;
 51         }
 52         //刪除當前節點
 53         public void remove() {
 54             checkForComodification();
 55             if (lastReturned == null)
 56                 throw new IllegalStateException();
 57 
 58             Node<E> lastNext = lastReturned.next;
 59             unlink(lastReturned);
 60             if (next == lastReturned)
 61                 next = lastNext;
 62             else
 63                 nextIndex--;
 64             lastReturned = null;
 65             expectedModCount++;
 66         }
 67         //修改當前節點的元素
 68         public void set(E e) {
 69             if (lastReturned == null)
 70                 throw new IllegalStateException();
 71             checkForComodification();
 72             lastReturned.item = e;
 73         }
 74         //在當前節點添加元素
 75         public void add(E e) {
 76             checkForComodification();
 77             lastReturned = null;
 78             if (next == null)
 79                 linkLast(e);
 80             else
 81                 linkBefore(e, next);
 82             nextIndex++;
 83             expectedModCount++;
 84         }
 85         
 86         //經過lambda表達式遍歷
 87         public void forEachRemaining(Consumer<? super E> action) {
 88             Objects.requireNonNull(action);
 89             while (modCount == expectedModCount && nextIndex < size) {
 90                 action.accept(next.item);
 91                 lastReturned = next;
 92                 next = next.next;
 93                 nextIndex++;
 94             }
 95             checkForComodification();
 96         }
 97      // 判斷expectedModCount和modCount是否一致,以確保經過ListItr的修改操做正確的反映在LinkedList中
 98         final void checkForComodification() {
 99             if (modCount != expectedModCount)
100                 throw new ConcurrentModificationException();
101         }
102     }

 

在使用迭代器遍歷時,咱們是不斷的改變當前的節點,並經過當前節點來獲取下一個節點來獲取元素的。

這樣在使用Iterator遍歷時,就不須要像for循環裏get方法同樣,每次獲取元素,都要對鏈表進行循環。由此能夠看出,LinkedList使用Iterator進行遍歷要比使用for循環進行遍歷效率要高。

  測試代碼以下:

 1 public static void main(String[] args) {
 2         //建立一個集合並添加100000個元素
 3         List<String> list = new LinkedList<String>();
 4         for (int i = 0; i < 100000; i++) {
 5             list.add(i+"");
 6         }
 7         //使用for循環遍歷
 8         long startTime1 = System.currentTimeMillis();
 9         for (int i = 0; i < list.size(); i++) {
10             System.out.println(list.get(i));
11         }
12         long endTime1 = System.currentTimeMillis();
13         //使用iterator遍歷
14         long startTime2 = System.currentTimeMillis();
15         Iterator<String> itr = list.iterator();
16         while(itr.hasNext()){
17             System.out.println(itr.next());
18         }
19         long endTime2 = System.currentTimeMillis();
20         System.out.println("=========================");
21         System.out.println("使用for循環遍歷時間:"+(endTime1-startTime1));
22         System.out.println("使用iterator遍歷時間:"+(endTime2-startTime2));
23     }

運行結果:

使用for循環遍歷時間:31086
使用iterator遍歷時間:987

LinkedList還提供了一個獲取迭代器的方法:descendingIterator();

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

而DescendingIterator是LinkedList的一個內部類,其代碼以下:

 1  private class DescendingIterator implements Iterator<E> {
 2         private final ListItr itr = new ListItr(size());
 3         public boolean hasNext() {
 4             return itr.hasPrevious();
 5         }
 6         public E next() {
 7             return itr.previous();
 8         }
 9         public void remove() {
10             itr.remove();
11         }
12     }

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


 

寫在最後:
  此篇隨筆僅用來記錄個人學習內容,若有錯誤,歡迎指正。謝謝!!!

相關文章
相關標籤/搜索