LinkedList詳解,看這篇就夠了

###一. 概述
LinkedList 是 Java 集合中比較常用的數據結構,與 ArrayList 一樣,實現了 List 接口,只不過 ArrayList 是基於數組實現的,而 LinkedList 是基於鏈表實現的。所以 LinkedList 插入和刪除方面要優於 ArrayList,而隨機訪問上則 ArrayList 性能更好。
除了 LIst 接口之外,LinkedList 還實現了 Deque,Cloneable,Serializable 三個接口。這說明該數據結構支持隊列,克隆和序列化操作的。與 ArrayList 一樣,允許 null 元素的存在,且是不支持多線程的。
###二. 源碼解讀

##屬性

LinkedList 提供了以下三個成員變量。size,first,last。

 transient int size = 0;
 transient Node<E> first;
 transient Node<E> last;

其中 size 爲 LinkedList 的大小,first 和 last 分別爲鏈表的頭結點和尾節點。Node 爲節點對象。

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;
    }
}

Node 是 LInkedList 的內部類,定義了存儲的數據元素,前一個節點和後一個節點,典型的雙鏈表結構。

##構造方法

public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

LinkedList 提供了兩個構造方法:LinkedList() 和 LinkedList(Collection&lt;? extends E&gt; c)
LinkedList() 僅僅構造一個空的列表,沒有任何元素。size = 0。first 和 last 都爲 null。
後一個構造方法構造一個包含指定 Collection 中所有元素的列表,該構造方法首先會調用空的構造方法,然後通過 addAll() 的方式把 Collection 中的所有元素添加進去。

  • 調用 addAll() 方法,傳入當前的節點個數 size,此時 size 爲
  • 檢查 index 是否越界
  • 將 collection 轉換成數組
  • 遍歷數組,將數組裏面的元素創建爲節點,並按照順序連起來。
  • 修改當前的節點個數 size 的值
  • 操作次數 modCount 自增 1.
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    Node<E> pred, succ;
    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);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}

##add 操作

添加元素到鏈表末尾

public boolean add(E e) {
    linkLast(e);
    return true;
}

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++;
}

add 方法直接調用了 linkLast 方法,而 linkLast 方法是不對外開放的。該方法做了三件事情,新增一個節點,改變其前後引用,將 size 和 modCount 自增 1。其中 modCount 是記錄對集合操作的次數。
在指定的位置插入元素

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

首先檢查下標是否越界,然後判斷如果 index == size 則添加到末尾,否則將該元素插入的 index 的位置。其中 node(index) 是獲取 index 位置的節點,linkBefore 負責把元素 e 插入到 succ 之前。

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

可以看出 node() 方法這裏寫的還是挺讚的,不是傻乎乎的從頭到尾或者從尾到頭遍歷鏈表,而是將 index 與 當前鏈表的一半做對比,比一半小從頭遍歷,比一半大從後遍歷。對於數據量很大時能省下不少時間。

##get 操作

很簡單,首先獲取節點,然後返回節點的數據即可。

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

##remove 操作

移除指定位置的元素

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next; // 如果移除的是頭節點,那麼頭結點後移
    } else {
        prev.next = next;
        x.prev = null;  // 釋放節點的前一個元素
    }

    if (next == null) {
        last = prev; // 如果移除的是尾節點,尾結點前移
    } else {
        next.prev = prev;
        x.next = null;  // 釋放節點的後一個元素
    }

    x.item = null; // 釋放節點數據
    size--;
    modCount++;
    return element;
}

先檢查下標是否越界,然後調用 unlink 釋放節點。
移除指定元素

public boolean remove(Object o) {
    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;
}

判斷要移除的元素是否爲 null,然後在遍歷鏈表,找到鈣元素第一次出現的位置,移除並返回 true。
像其他的常用方法如:getFirst, getLast, removeFirst, removeLast, addFirst, addLast 等都很簡單,掃一眼源碼就能懂,我這裏就不寫了。

迭代器

LInkedList 的 iterator() 方法是在其父類 AbstractSequentialList 中定義的,最終一路 debug 到 LinkedList 類這裏。其中 index 爲 零。

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

我們來看看 ListItr。

private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

     final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

篇幅有限 ,我就只貼主要代碼了。由源碼可以看出初始化 ListItr 時,將 nextIndex 指向 index, 也就是零。如果該集合爲空,那麼 index == size 爲 true,next 指向 null,否則 next 指向下標爲零的元素,也就是第一個。
hasNext 直接返回 nextIndex < size 簡單明瞭。下面看看 next 方法,首先檢查 expectedModCount 與 modCount 是否相等,看似無關緊要的代碼保證了集合在迭代過程中不被修改[包括新增刪除節點等]。然後將 lastReturned 指向 next,next 後移一個節點,nextIndex 自增 1,並返回 lastReturned 節點的元素。
###總結

1、從源碼可以看出 LinkedList 是基於鏈表實現的。如下圖:
這裏寫圖片描述

2、在查找和刪除某元素時,區分該元素爲 null和不爲 null 兩種情況來處理,LinkedList 中允許元素爲 null。

3、基於鏈表實現不存在擴容問題。

4、查找時先判斷該節點位於前半部分還是後半部分,加快了速度

5、因爲基於鏈表,所以插入刪除極快,查找比較慢。

6、實現了棧和隊列的相關方法,所以可作爲棧,隊列,雙端隊列來用。

作者:coderlmm
鏈接:https://juejin.im/post/5a90c2fd6fb9a06361089023

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章