重新認識java-LinkedList 頂 原

源代碼解讀

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

功能和特點

  1. 繼承了一個抽象類AbstractSequentialList,這個類就是用調用ListIterator實現了元素的增刪查改,這些方法在LinkedList中被複寫。
  2. LinkedList實現了ListDequeCloneable以及Serializable接口。其中Deque是雙端隊列接口,所以LinkedList可以當作是棧、隊列或者雙端隊隊列。

LinkedList實現

主要原理

元素在內部被封裝成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表示的是雙向鏈表的節點數據結構。 使用雙向循環鏈表實現,初始化時,雙向循環鏈表的first=last=null, 添加第一個元素: 即使得firt=last=newNode。 再次添加元素時:

pred = last;
pred.next = newNode2;
pred.next.prev = pred;
pred = newNode2;
last = pred;

變量

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

size是元素個數,first是雙向鏈表的第一個元素,last雙向鏈表的最後一個元素。

構造函數

public LinkedList() {
}

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

一個默認的的構造函數; 一個支持添加集合中多有元素到LinkedList中。 addAll()方法如下,

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

當不指定元素添加的位置時,默認添加到last Node的後面(即index=size,size表示LInkedList的元素個數);否則將集合中的所有元素插入到LinkedList中。 主要是通過`node(int index)找到指定位置的Node對象,具體實現如下:

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

此處技巧,通過index與size向左移一位(即$size/2$)比較,判斷最快找到index位置元素是從頭還是從尾部開始查找,時間複雜度有$O(n)降低爲$O(n/2)+O(1)$。

核心私有方法

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)//LinkedList中無節點
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}
//尾部插入
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)    //如果鏈表原來爲空,讓first指向這個唯一的節點
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
//中間插入
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)//succ是first節點
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}
//刪除頭節點
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next; //先保存下一個節點
    f.item = null;    
    f.next = null; // help GC
    first = next;    //讓first指向下一個節點
    if (next == null)    //如果下一個節點爲空,說明鏈表原來只有一個節點,現在成空鏈表了,要把last指向null
        last = null;
    else        //否則下一個節點的前驅節點要置爲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 = prev;    //last指向前一個節點
    if (prev == null)    //與頭節點刪除一樣,判斷是否爲空
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}
//從鏈表中間刪除節點
 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要指向下一個節點
        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;
}

LinkedList的隊列方法實現和主要的增刪查改都是基於上述幾個方法。

核心public方法

公開的方法幾乎都是調用上面幾個私有方法實現的。 add

public boolean add(E e) {
    linkLast(e);
    return true;
}
public boolean add(E e) {
    linkLast(e);
    return true;
}
public void add(int index, E element) {
    checkPositionIndex(index);

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

這些方法的實現都很簡單。注意最後一個方法add(int index, E element),這個方法是在指定的位置插入元素。首先判斷位置是否越界,然後判斷是不是最後一個位置。如果是就直接插入鏈表末尾,否則調用linkBefore(element, node(index)方法。這裏在傳參數的時候又調用了node(index),這個方法的目的是找到這個位置的節點對象。 用於查找指定位置元素的get(int index)方法也是調用node實現的:

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

remove

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

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

第一個remove(int index)方法同樣要調用node(index)尋找節點。而第二個方法remove(Object o)是刪除指定元素,這個方法要依次遍歷節點進行元素的比較,最壞情況下要比較到最後一個元素,比調用node方法更慢,時間複雜度爲$O(n)$。

可以看出LinkedList的元素可以是null。

總結

  1. LinkedList基於雙向鏈表實現,元素可以爲null。
  2. 對添加和刪除元素的時間複雜度認爲是$O(1)$,而查找某個元素的時間複雜度較高。適合添加和刪除較頻繁的使用場景。

Thanks for reading! want more

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