Java容器系列-LinkedList 原始碼分析

Rayjun發表於2019-12-14

LinkedList 作為 List 的另一種實現,也非常的經典。與 ArrayList 不同,LinkedList 底層使用的是雙向連結串列來實現的,具體類圖如下:

Java容器系列-LinkedList 原始碼分析

相比於 ArrayList,LinkedList 繼承了 AbstractSequentialList 類,而且實現了 Deque 介面,RandomAccess 介面就被沒有實現。

但在實際的使用當中,LinkedList 使用的並沒有 ArrayList 多,LinkedList 可以被當做佇列和棧來使用,但是 BlockingQueue 使用的比它更為廣泛,因為一般使用佇列的地方都會涉及到比較高的併發,在高併發的情況下,BlockingQueue 比 LinkedList 更好用,BlockingQueue 以後會寫專門的文章來介紹。

本文基於 JDK1.8

成員變數

LinkedList 的結構比 ArrayList 更簡單,核心的成員變數如下,size 記錄當前元素的個數,first 指向頭結點,last 指向尾節點。

transient int size = 0;
transient Node<E> first;
transient Node<E> last;
複製程式碼

Node 的程式碼如下,通過泛型來儲存具體的元素,每個節點都可以獲取前一個或者後一個節點,所以 LinkedNode 底層資料結構是雙向連結串列

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 的例項化過程也相對簡單,只提供了兩個建構函式。

一個不帶任何引數,也不需要做任何的資料初始化,頭結點和尾節點的初始化都放在新增第一個元素的時候。

public LinkedList() {
}
複製程式碼

第二個建構函式接收一個 Collection 型別的物件,會把物件中的所有元素都新增到當前 LinkedList 物件中。

 public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
 }
複製程式碼

具體實現

與 ArrayList 相比,LinkedList 有如下特點:

  • 不能隨機訪問
  • 容量無限
  • 可以用作佇列雙向佇列

因為 LinkedList 沒有實現 RandomAccess 介面,再加上本身底層的資料結構是雙向連結串列,所以對連結串列中的元素不能隨機訪問,只能按照順序訪問。

而且對於連結串列來說,元素時可以無限擴充套件(理論上)的,所以 LinkedList 的容量也沒有上限。

從 JDK1.6 開始,LinkedList 實現了 Deque 介面,這就表明 LinkedList 可以用作佇列或者雙向佇列

增刪改查方法

List 中有的方法,LinkedList 中都實現了。

可以新增元素:

public boolean add(E e) {
    linkLast(e);
    return true;
}
複製程式碼

可以看到,新增元素的操作實際上是用 linkLast() 方法來完成的,在新增元素的過程中,如果發現頭結點為空,那說明是新增第一個元素,只需要把頭結點指向剛新增的節點就可以,以下程式碼是在尾部新增一個節點:

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
複製程式碼

上面是把元素新增到連結串列的尾部,因為 LinkedList 還可以被用作佇列和棧,因此還提供了從頭部新增元素的方法:

 private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}
複製程式碼

既然有新增元素的操作,也有刪除元素的操作,程式碼如下:

private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null;
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}
複製程式碼

清空 LinkedList 就是把所有的元素置為 null:

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 的操作都是對連結串列的操作。

用作佇列和棧

做為普通佇列時,可以在佇列中進行入隊和出隊的操作。從佇列頭部獲取一個元素,但是不刪除元素 peek():

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
複製程式碼

從佇列頭部獲取一個元素並刪除,poll():

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}
複製程式碼

在佇列的尾部新增一個元素,offer():

 public boolean offer(E e) {
    return add(e);
 }
複製程式碼

普通佇列只可以在一端入隊,在另一端出隊,但是對於雙向佇列,可以在兩端執行入隊和出隊操作。

所以在在作為雙向佇列時,拿 peek 操作來說即可以 peekFirst() 也可以 peekLast()。其他的操作例如 offer、poll 同樣類似。

LinkedList 中還有 push()pop() 操作。被當做棧使用時,只需要對頭部節點進行操作就行。

入棧操作:

public void push(E e) {
    addFirst(e);
}
複製程式碼

出棧操作:

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

其他功能

LinkedList 中也實現了 ListIterator 和 Spliterator 介面。

所以 LinkedList 也可以從兩端進行遍歷。在遍歷的時候,同樣也有 fail-fast 機制來檢查遍歷的過程當中,容器中的元素是否被修改。

在實現 Spliterator 介面之後,也可以對容器中的元素進行分段,然後同時讓多個執行緒同時進行處理,提高處理效率。分割 LinkedList 的程式碼如下:

LinkedList<Integer> list = new LinkedList<>();

    for (int i = 0; i < 20; i++) {
        list.add(i);
    }

    Spliterator<Integer> splitor = list.spliterator();
    Spliterator<Integer> s1 = splitor.trySplit();
    Spliterator<Integer> s2 = s1.trySplit();

    System.out.println(s1.estimateSize()); // 10
    System.out.println(s2.estimateSize()); // 10
複製程式碼

(完)

原文

相關文章

關注微信公眾號,聊點其他的

Java容器系列-LinkedList 原始碼分析

相關文章