搞懂 Java LinkedList 源碼

LinkedList 源碼分析

因爲最近工做有點忙,進行了 APP 的部分優化,期間也學習了不少有關於佈局優化和其餘性能優化的知識,可是仍然以爲不太成體系,期待能有更多的優質的性能優化實戰文章可以涌現出來,以便於你們一塊兒交流學習。html

週末有時間把手頭的工做放一放,來繼續進行 Java 集合源碼的學習。今天來學習下 「LinkedList」的源碼。node

  1. LinkedList 的概述
  2. LinkedList 的構造方法
  3. LinkedList 的增刪改查。
  4. LinkedList 做爲隊列(Queue)使用的時候增刪改查。
  5. LinkedList 的遍歷方法

LinkedList 的概述

先來看下 LinkedList 的繼承體系圖,這裏悄悄告訴你們一個方法在學習源碼的時候如何查看一個類的繼承體系的方法,第一步打開 IntelliJ IDEA 找到你要查看的類 ,第二步點擊右鍵,選擇 Diagrams 選擇二級菜單的任意一項,就能夠獲得下面這樣一個體系圖,還有好多方便的操做,你們能夠經過這篇文章來了解下 使用IntelliJ IDEA查看類的繼承關係圖形面試

 

 

圖中藍色實線箭頭是指繼承關係 ,綠色虛線箭頭是指接口實現關係。數組

  1. LinkedList 繼承自 AbstrackSequentialList 並實現了 List 接口以及 Deque 雙向隊列接口,所以 LinkedList 不但擁有 List 相關的操做方法,也有隊列的相關操做方法。安全

  2. LinkedListArrayList 同樣實現了序列化接口 SerializableCloneable 接口使其擁有了序列化和克隆的特性。性能優化

LinkedList 一些主要特性:數據結構

  1. LinkedList 集合底層實現的數據結構爲雙向鏈表
  2. LinkedList 集合中元素容許爲 null
  3. LinkedList 容許存入重複的數據
  4. LinkedList 中元素存放順序爲存入順序。
  5. LinkedList 是非線程安全的,若是想保證線程安全的前提下操做 LinkedList,可使用 List list = Collections.synchronizedList(new LinkedList(...)); 來生成一個線程安全的 LinkedList

鏈表是一種不一樣於數組的數據結構,雙向鏈表是鏈表的一種子數據結構,它具備如下的特色:app

每一個節點上有三個字段:當前節點的數據字段(data),指向上一個節點的字段(prev),和指向下一個節點的字段(next)。框架

LLink Data RLink

LinkedList 雙向鏈表實現及成員變量

概述上說了雙向鏈表的特色,而 LinkedList 又繼承自 Deque 這個雙鏈表接口,在介紹 LinkedList 的具體方法前咱們先了解下雙向鏈表的實現。函數

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 的節點實現徹底符合雙向鏈表的數據結構要求,而構造方法第一個參數爲上一個節點的索引,當前節點的元素,下一個節點索引。

LinkedList 主要成員變量有下邊三個:

//LinkedList 中的節點個數
transient int size = 0;

//LinkedList 鏈表的第一個節點
transient Node<E> first;

//LinkedList 鏈表的最後一個節點
transient Node<E> last;

複製代碼

之因此 LinkedList 要保存鏈表的第一個節點和最後一個節點是由於,咱們都知道,鏈表數據結構相對於數組結構,有點在於增刪,缺點在於查找。若是咱們保存了LinkedList 的頭尾兩端,當咱們須要以索引來查找節點的時候,咱們能夠根據 indexsize/2 的大小,來決定從頭查找仍是從尾部查找,這也算是必定程度上彌補了單鏈表數據結構的缺點。

LinkedList 的構造函數

LinkedList 有兩個構造函數:

/**
 * 空參數的構造因爲生成一個空鏈表 first = last = null
 */
 public LinkedList() {
 }

/**
 * 傳入一個集合類,來構造一個具備必定元素的 LinkedList 集合
 * @param  c  其內部的元素將按順序做爲 LinkedList 節點
 * @throws NullPointerException 若是 參數 collection 爲空將拋出空指針異常
 */
public LinkedList(Collection<? extends E> c) {
   this();
   addAll(c);
}
複製代碼

帶參數的構造方法,調用 addAll(c) 這個方法,實際上這方法調用了 addAll(size, c) 方法,在外部單獨調用時,將指定集合的元素做爲節點,添加到 LinkedList 鏈表尾部: 而 addAll(size, c) 能夠將集合元素插入到指定索引節點。

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
複製代碼
/**
 * 在 index 節點前插入包含全部 c 集合元素的節點。
 * 返回值表示是否成功添加了對應的元素.
 */
public boolean addAll(int index, Collection<? extends E> c) {
   // 查看索引是否知足 0 =< index =< size 的要求
   checkPositionIndex(index);
    // 調用對應 Collection 實現類的 toArray 方法將集合轉爲數組
   Object[] a = c.toArray();
   //檢查數組長度,若是爲 0 則直接返回 false 表示沒有添加任何元素
   int numNew = a.length;
   if (numNew == 0)
       return false;
   // 保存 index 當前的節點爲 succ,當前節點的上一個節點爲 pred
   Node<E> pred, succ;
   // 若是 index = size 表示在鏈表尾部插入
   if (index == size) {
       succ = null;
       pred = last;
   } else {
       succ = node(index);
       pred = succ.prev;
   }
    
    // 遍歷數組將對應的元素包裝成節點添加到鏈表中
   for (Object o : a) {
       @SuppressWarnings("unchecked") E e = (E) o;
       Node<E> newNode = new Node<>(pred, e, null);
       //若是 pred 爲空表示 LinkedList 集合中尚未元素
       //生成的第一個節點將做爲頭節點 賦值給 first 成員變量
       if (pred == null)
           first = newNode;
       else
           pred.next = newNode;
       pred = newNode;
   }
   // 若是 index 位置的元素爲 null 則遍歷數組後 pred 所指向的節點即爲新鏈表的末節點,賦值給 last 成員變量
   if (succ == null) {
       last = pred;
   } else {
       // 不然將 pred 的 next 索引指向 succ ,succ 的 prev 索引指向 pred
       pred.next = succ;
       succ.prev = pred;
   }
   // 更新當前鏈表的長度 size 並返回 true 表示添加成功
   size += numNew;
   modCount++;
   return true;
}
複製代碼

通過上邊的代碼註釋能夠了解到,LinkedList 批量添加節點的方法實現了。大致分下面幾個步驟:

  1. 檢查索引值是否合法,不合法將拋出角標越界異常
  2. 保存 index 位置的節點,和 index-1 位置的節點,對於單鏈表熟悉的同窗必定清楚對於鏈表的增刪操做都須要兩個指針變量完成,可參考:搞懂單鏈表面試題 來深刻理解下。
  3. 將參數集合轉化爲數組,循環將數組中的元素封裝爲節點添加到鏈表中。
  4. 更新鏈表長度並返回添加 true 表示添加成功。

對於 checkPositionIndex方法這裏想順帶分析了,LinkedList 中有兩個方法用於檢查角標越界,內部實現同樣,都是經過判斷 index >= 0 && index < size 是否知足條件。

private String outOfBoundsMsg(int index) {
   return "Index: "+index+", Size: "+size;
}

private void checkElementIndex(int index) {
   if (!isElementIndex(index))
       throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private void checkPositionIndex(int index) {
   if (!isPositionIndex(index))
       throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
* Tells if the argument is the index of an existing element.
*/
private boolean isElementIndex(int index) {
   return index >= 0 && index < size;
}

/**
* Tells if the argument is the index of a valid position for an
* iterator or an add operation.
*/
private boolean isPositionIndex(int index) {
   return index >= 0 && index <= size;
}
複製代碼

LinkedList 的增刪改查

LinkedList 添加節點的方法

LinkedList 做爲鏈表數據結構的實現,不一樣於數組,它能夠方便的在頭尾插入一個節點,而 add 方法默認在鏈表尾部添加節點:

/**
 * Inserts the specified element at the beginning of this list.
 *
 * @param e the element to add
 */
 public void addFirst(E e) {
    linkFirst(e);
 }

/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #add}.
 *
 * @param e the element to add
 */
 public void addLast(E e) {
    linkLast(e);
 }
    
/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
 public boolean add(E e) {
    linkLast(e);
    return true;
 }
複製代碼

上述英文太過簡單不翻譯了,咱們能夠看到 add 方法是有返回值的,這個能夠注意下。看來這一系方法都調用用了 linkXXX 方法,

/**
  * 添加一個元素在鏈表的頭節點位置
  */
private void linkFirst(E e) {
   // 添加元素以前的頭節點
   final Node<E> f = first;
   //以添加的元素爲節點值構建新的頭節點 並將 next 指針指向 以前的頭節點
   final Node<E> newNode = new Node<>(null, e, f);
   // first 索引指向將新的節點
   first = newNode;
   // 若是添加以前鏈表空則新的節點也做爲未節點
   if (f == null)
       last = newNode;
   else
       f.prev = newNode;//不然以前頭節點的 prev 指針指向新節點
   size++;
   modCount++;//操做數++
}

/**
 * 在鏈表末尾添加一個節點
 */
 void linkLast(E e) {
   final Node<E> l = last;//保存以前的未節點
   //構建新的未節點,並將新節點 prev 指針指向 以前的未節點
   final Node<E> newNode = new Node<>(l, e, null);
   //last 索引指向末節點
   last = newNode;
   if (l == null)//若是以前鏈表爲空則新節點也做爲頭節點
       first = newNode;
   else//不然將以前的未節點的 next 指針指向新節點
       l.next = newNode;
   size++;
   modCount++;//操做數++
}
複製代碼

除了上述幾種添加元素的方法,以及以前在將構造的時候說明的 addAll 方法,LinkedList 還提供了 add(int index, E element); 方法,下面咱們來看在這個方法:

/**
 * 在指定 index 位置插入節點
 */
public void add(int index, E element) {
   // 檢查角標是否越界
   checkPositionIndex(index);
   // 若是 index = size 表明是在尾部插入節點
   if (index == size)
       linkLast(element);
   else
       linkBefore(element, node(index));
}
複製代碼

能夠先看到當 0 =< index <size 的時候調用了 linkBefore(element, node(index))方法,咱們先來看下 node(index) 方法的實現:

/**
 * 返回一個非空節點,這個非空節點位於 index 位置
 */
 Node<E> node(int index) {
   // assert isElementIndex(index);
    // 若是 index < size/2 則從0開始尋找指定角標的節點
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
         // 若是 index >= size/2 則從 size-1 開始尋找指定角標的節點
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
 }
複製代碼

你們可能會疑惑爲何這裏註釋爲返回一個非空節點?其實仔細想下就明白了,這裏的節點必定不爲 null,若是一開始鏈表爲空的時候,index 爲 0 的位置確定爲 null,爲何不會產生異常狀況呢?其實若是一開始鏈表中沒有元素 size = 0,若是咱們向 index = 0 的位置添加元素是不會走到 else 中的,而是會調用 linkLast(element); 方法去添加元素。 所以 node 方法能夠用於根據指定 index 去以 size/2 爲界限搜索index 位置的 Node;

咱們再看回 linkBefore 方法,爲何要叫作 linkBefore 呢,由於在鏈表的中間位置添加節點,其實就是講 index 原來的節點前添加一個節點,添加節點咱們須要知道該節點的前一個節點和當前節點,

  1. 將構造的新節點前指針 prev 指向 index 的前一個元素,
  2. 新節點前指針 next 指針指向 index 位置的節點,
  3. index 位置節點 prev 指針指向新節點
  4. index 位置前節點(pred)的 next 指針指向新節點。

linkBefore 也是作了上述四件事:

void linkBefore(E e, Node<E> succ) {
   // assert succ != null;
   // 因爲 succ 必定不爲空,因此能夠直接獲取 prev 節點
   final Node<E> pred = succ.prev;
   // 新節點 prev 節點爲 pred,next 節點爲 succ
   final Node<E> newNode = new Node<>(pred, e, succ);
   // 原節點的 prev 指向新節點
   succ.prev = newNode;
   // 若是 pred 爲空即頭節點出插入了一個節點,則將新的節點賦值給 first 索引
   if (pred == null)
       first = newNode;
   else
       pred.next = newNode;//不然 pred 的下一個節點改成新節點
   size++;
   modCount++;
}
複製代碼

LinkedList 刪除節點的方法

與添加節點方法對應的就是刪除節點方法:

/**
 *  刪除頭節點
 * @return 刪除的節點的值 即 節點的 element
 * @throws NoSuchElementException 若是鏈表爲空則拋出異常
 */
 public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
 }

/**
 *  刪除尾節點
 *
 * @return  刪除的節點的值 即 節點的 element
 * @throws NoSuchElementException  若是鏈表爲空則拋出異常
 */
 public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
 }
 
複製代碼

能夠看出最終調用的方法爲 unlinkFirstunlinkLast 方法:

/**
 * 移除頭節點
 */
 private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // 頭節點的 element 這裏做爲返回值使用
    final E element = f.item;
    // 頭節點下個節點
    final Node<E> next = f.next;
    // 釋放頭節點的 next 指針,和 element 下次 gc 的時候回收這個內部類
    f.item = null;
    f.next = null; // help GC
    // 將 first 索引指向新的節點
    first = next;
    // 若是 next 節點爲空,即鏈表只有一個節點的時候,last 指向 null
    if (next == null)
        last = null;
    else
        next.prev = null; //不然 next 的 prev 指針指向 null
    size--;//改變鏈表長度
    modCount++;//修改操做數
    return element;//返回刪除節點的值
 }

/**
 * 移除未節點
 */
 private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    //未節點的前一個節點,
    final Node<E> prev = l.prev;
    //釋放未節點的內容
    l.item = null;
    l.prev = null; // help GC
    //將 last 索引指向新的未節點
    last = prev;
    // 鏈表只有一個節點的時候,first 指向 null
    if (prev == null)
       first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
 }
複製代碼

上邊咱們說過在指定位置添加的節點時候的是4個步驟,移除頭尾節點是兩個特殊的節點,可是整體來講仍是同樣的。下面看到 unlink(node(index))就是這樣的:

 

 

/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
   // assert x != null;
   final E element = x.item;
   //保存 index 節點的先後兩個節點
   final Node<E> next = x.next;
   final Node<E> prev = x.prev;
    // 若是節點爲頭節點,則作 unlinkFirst 相同操做
   if (prev == null) {
       first = next;
   } else {//不然將上一個節點的 next 指針指向下個節點
       prev.next = next;
       // 釋放 index 位置 prev 指針
       x.prev = null;
   }
    // 若是節點爲尾節點,則將 last 索引指向上個節點
   if (next == null) {
       last = prev;
   } else {//不然下個節點 prev 指針指向上個節點
       next.prev = prev;
       x.next = null;
   }

   x.item = null;
   size--;
   modCount++;
   return element;
}
複製代碼

看完 unlink 操做結合以前說的 node(index),下邊兩種刪除節點的操做,就很好理解了

/**
 * 刪除指定索引位置的節點
 */
public E remove(int index) {
   checkElementIndex(index);
   return unlink(node(index));
}

/**
 *刪除從頭節點其第一個與 o 相同的節點
 */
public boolean remove(Object o) {
    // 區別對待 null 元素,比較元素時候使用 == 而不是 equals
   if (o == null) {
       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;
}
複製代碼

看完單個刪除節點的方法 LinkedList 實現了 List 接口的 clear 操做,用於刪除鏈表全部的節點:

/**
* Removes all of the elements from this list.
* The list will be empty after this call returns.
*/
public void clear() {
   // 依次清除節點,幫助釋放內存空間
   for (Node<E> x = first; x != null; ) {
       Node<E> next = x.next;
       x.item = null;
       x.next = null;
       x.prev = null;
       x = next;
   }
   first = last = null;
   size = 0;
   modCount++;
}
複製代碼

LinkedList 查詢節點的方法

LinkedList 查詢節點的方法,可分爲根據指定的索引查詢,獲取頭節點,獲取未節點三種。值得注意的是,根據索引去獲取節點內容的效率並不高,因此若是查詢操做多餘增刪操做的時候建議用 ArrayList 去替代。

/**
* 根據索引查詢
*
public E get(int index) {
   checkElementIndex(index);
   return node(index).item;
}

/**
* 返回 first 索引指向的節點的內容
*
* @return the first element in this list
* @throws NoSuchElementException 若是鏈表爲空則拋出異常
*/
public E getFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return f.item;
}

/**
* 返回 last 索引指向的節點的內容
*
* @return the last element in this list
* @throws NoSuchElementException 若是鏈表爲空則拋出異常
*/
public E getLast() {
   final Node<E> l = last;
   if (l == null)
       throw new NoSuchElementException();
   return l.item;
}

複製代碼

LinkedList 的修改節點方法

修改節點也分爲修改指定索引的節點內容和修改頭節點內容,未節點內容的方法? 哈哈,理所因當了,其實LinkedList 只提供了 set(int index, E element) 一個方法。

public E set(int index, E element) {
   // 判斷角標是否越界
   checkElementIndex(index);
   // 採用 node 方法查找對應索引的節點
   Node<E> x = node(index);
   //保存節點原有的內容值
   E oldVal = x.item;
   // 設置新值
   x.item = element;
   // 返回舊的值
   return oldVal;
}

複製代碼

LinkedList 的元素查詢方法

上邊的咱們知道 LinkedList提供根據角標查詢節點的方法,LinkedList 還提供了一系列判斷元素在鏈表中的位置的方法。

/* 
* 返回參數元素在鏈表的節點索引,若是有重複元素,那麼返回值爲從**頭節點**起的第一相同的元素節點索引,
* 若是沒有值爲該元素的節點,則返回 -1;
* 
* @param o element to search for
* @return 
*/
public int indexOf(Object o) {
   int index = 0;
  // 區別對待 null 元素,用 == 判斷,非空元素用 equels 方法判斷 
   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;
}

/**
**返回參數元素在鏈表的節點索引,若是有重複元素,那麼返回值爲從**尾節點起**的第一相同的元素節點索引,
* 若是沒有值爲該元素的節點,則返回 -1;
*
* @param o element to search for
* @return the index of the last occurrence of the specified element in
*         this list, or -1 if this list does not contain the element
*/
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;
}
複製代碼

兩個方法分別返回從頭節點起第一個與參數元素相同的節點索引,和從尾節點起第一個與參數元素相同的節點索引。

除了上述兩個方法咱們開能夠調用 contains(Object o) 來判斷鏈表中是否有該元素存在。調用 indexOf 從頭結點開始查詢元素位置遍歷完成後若 返回值 !=-1 則表示存在,反之不存在

public boolean contains(Object o) {
    return indexOf(o) != -1;
}
複製代碼

LinkedList 做爲雙向隊列的增刪改查

分析完 LinkedList 做爲 List 集合的增刪改查操做,咱們看下 LinkedList 是如何實現 Deque 接口的方法的:

Deque 雙端隊列

咱們先來認識一下 Java 中的 雙端隊列,咱們都知道 Queue 是一個隊列,遵循 FIFO 準則,咱們也知道 Stack 是一個棧結構,遵循 FILO 準則。 而Deque 這個雙端隊列就厲害了,它既能夠實現棧的操做,也能夠實現隊列的操做,換句話說,實現了這個接口的類,既能夠做爲棧使用也能夠做爲隊列使用。

咱們來看下 Queue 給咱們提供了的方法:

  頭部 頭部 尾部 尾部
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() remveLast() pollLast
獲取 getFirst() peekFirst() getLast() peekLast

因爲 Deque 接口繼承 Queue 接口,當 Deque 當作隊列使用時(FIFO),只須要在頭部刪除,尾部添加便可。咱們如今複習下 Queue 中的方法及區別:

  1. Queueofferadd 都是在隊列中插入一個元素,具體區別在於,對於一些 Queue 的實現的隊列是有大小限制的,所以若是想在一個滿的隊列中加入一個新項,多出的項就會被拒絕。此時調用 add()方法會拋出異常,而 offer() 只是返回的 false。

  2. remove()poll() 方法都是從隊列中刪除第一個元素。remove()也將拋出異常,而 poll() 則會返回 null

  3. element()peek() 用於在隊列的頭部查詢元素。在隊列爲空時, element() 拋出一個異常,而 peek() 返回 null

上述方法的區別對於 Queue 對應的實現類的對應方法,是一種規定,本身在實現 Queue 隊列的時候也要遵循此規則。

咱們經過下邊的表格來對照下雙端隊列是如何實現隊列操做的,值得注意的是 Deque 實現了 Queue,因此 Queue 全部的方法 Deque 都有,下面比較的是Deque區別 Queue 的方法:

Queue Deque
add(e) addLast()
offer(e) offerLast()
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

由上表咱們能夠看到 Deque 對應的 Queue 的方法,那麼對於他們的實現類 LinkedList 是怎麼實現的呢?

Deque 和 Queue 添加元素的方法

咱們對比下對應的實現方法:

// queue 的添加方法實現,
public boolean add(E e) {
   linkLast(e);
   return true;
}
// Deque 的添加方法實現,
public void addLast(E e) {
   linkLast(e);
} 
  
// queue 的添加方法實現,
public boolean offer(E e) {
   return add(e);
}

// Deque 的添加方法實現,
public boolean offerLast(E e) {
        addLast(e);
        return true;
}
    
複製代碼

上面說起到 Queueofferadd 的區別針對容量有限制的實現,很明顯 LinkedList 的大小並無限制,因此在 LinkedList 中他們的實現並無實質性不一樣。

Deque 和 Queue 刪除元素的方法

// Queue 刪除元素的實現 removeFirst 會拋出 NoSuchElement 異常
public E remove() {
   return removeFirst();
}

// Deque 的刪除方法實現
public E removeFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return unlinkFirst(f);
}
    
// Queue 刪除元素的實現 不會拋出異常 若是鏈表爲空則返回 null 
public E poll() {
   final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);
}

// Deque 刪除元素的實現 不會拋出異常 若是鏈表爲空則返回 null 
public E pollFirst() {
   final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);
}
複製代碼

Deque 和 Queue 獲取隊列頭部元素的實現

// Queue 獲取隊列頭部的實現 隊列爲空的時候回拋出異常
 public E element() {
    return getFirst();
 }
// Deque 獲取隊列頭部的實現 隊列爲空的時候回拋出異常
public E getFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return f.item;
}

// Queue 獲取隊列頭部的實現 隊列爲空的時候返回 null
public E peek() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

// Deque 獲取隊列頭部的實現 隊列爲空的時候返回 null
public E peekFirst() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}
複製代碼

上述咱們分析了,雙端隊列做爲隊列使用的時候的各個方法的區別,也但是看出 LinkedList 對對應方法的實現,遵循了隊列設計原則。

下面咱們來看看下雙端隊列做爲棧 Stack使用的時候方法對應關係,與 Queue 不一樣,Stack 自己就是實現類,他擁有 FILO 的原則, Stack 的入棧操做經過 push 方法進行,出棧操做經過 pop 方法進行,查詢操做經過 peek 操做進行。 Deque 做爲棧使用的時候,也遵循 FILO 準則,入棧和出棧是經過添加和移除頭節點實現的。

Stack Deque
push(e) addFist(e)
pop() removeFirst()
peek() peekFirst()

因爲分析隊列的時候已經分析了addFistremoveFirstpeekFirst操做的方法了,下邊咱們來顯 push 和 pop 的實現:

public void push(E e) {
   addFirst(e);
}

public E pop() {
   return removeFirst();
}
複製代碼

哇,毫無遮掩的直接調用了 addFirstremoveFirst 方法。這樣看來沒啥好分析的了。

LinkedList 的遍歷

ArrayList 分析的時候,咱們就知道 List 的實現類,有4中遍歷方式:for 循環,高級 for 循環,Iterator 迭代器方法, ListIterator 迭代方法。因爲 ArrayList 源碼分析的時候比較詳細看了源碼,對於不一樣數據結構的 LinkedList 咱們只看下他們的不一樣之處.

LinkedList 沒有單獨 Iterator 實現類,它的 iteratorlistIterator 方法均返回 ListItr的一個對象。 LinkedList 做爲雙向鏈表數據結構,過去上個元素和下個元素很方便。

下邊咱們來看下 ListItr 的源碼:

private class ListItr implements ListIterator<E> {
   // 上一個遍歷的節點
   private Node<E> lastReturned;
   // 下一次遍歷返回的節點
   private Node<E> next;
   // cursor 指針下一次遍歷返回的節點
   private int nextIndex;
   // 指望的操做數
   private int expectedModCount = modCount;
    
   // 根據參數 index 肯定生成的迭代器 cursor 的位置
   ListItr(int index) {
       // assert isPositionIndex(index);
       // 若是 index == size 則 next 爲 null 不然尋找 index 位置的節點
       next = (index == size) ? null : node(index);
       nextIndex = index;
   }

   // 判斷指針是否還能夠移動
   public boolean hasNext() {
       return nextIndex < size;
   }
    
  // 返回下一個帶遍歷的元素
  public E next() {
       // 檢查操做數是否合法
       checkForComodification();
       // 若是 hasNext 返回 false 拋出異常,因此咱們在調用 next 前應先調用 hasNext 檢查
       if (!hasNext())
           throw new NoSuchElementException();
        // 移動 lastReturned 指針
       lastReturned = next;
        // 移動 next 指針
       next = next.next;
       // 移動 nextIndex cursor
       nextIndex++;
       // 返回移動後 lastReturned
       return lastReturned.item;
   }

  // 當前遊標位置是否還有前一個元素
   public boolean hasPrevious() {
       return nextIndex > 0;
   }
  
  // 當前遊標位置的前一個元素
   public E previous() {
       checkForComodification();
       if (!hasPrevious())
           throw new NoSuchElementException();
        // 等同於 lastReturned = next;next = (next == null) ? last : next.prev;
        // 發生在 index = size 時
       lastReturned = next = (next == null) ? last : next.prev;
       nextIndex--;
       return lastReturned.item;
   }
    
   public int nextIndex() {
       return nextIndex;
   }

   public int previousIndex() {
       return nextIndex - 1;
   }
    
    // 刪除鏈表當前節點也就是調用 next/previous 返回的這節點,也就 lastReturned
   public void remove() {
       checkForComodification();
       if (lastReturned == null)
           throw new IllegalStateException();

       Node<E> lastNext = lastReturned.next;
       //調用LinkedList 的刪除節點的方法
       unlink(lastReturned);
       if (next == lastReturned)
           next = lastNext;
       else
           nextIndex--;
       //上一次所操做的 節點置位空    
       lastReturned = null;
       expectedModCount++;
   }

    // 設置當前遍歷的節點的值
   public void set(E e) {
       if (lastReturned == null)
           throw new IllegalStateException();
       checkForComodification();
       lastReturned.item = e;
   }
    // 在 next 節點位置插入及節點
   public void add(E e) {
       checkForComodification();
       lastReturned = null;
       if (next == null)
           linkLast(e);
       else
           linkBefore(e, next);
       nextIndex++;
       expectedModCount++;
   }
    //簡單哈操做數是否合法
   final void checkForComodification() {
       if (modCount != expectedModCount)
           throw new ConcurrentModificationException();
   }
}
複製代碼

參考下圖理解下,LinkedList 的迭代器的三個變量。

 

 

 

 

 

 

總結

本文從 LinkedList 的源碼出發,分析了LinkedList 集合常見的操做,以及它做爲隊列或者棧的時候增刪改查方法。是繼上一篇 ArrayList源碼分析後的第二篇集合框架源碼分析。

相關文章
相關標籤/搜索