Java集合源碼分析之LinkedList_一點課堂(多岸學院)

分析完了ListQueue以後,終於能夠看看LinkedList的實現了。LinkedList彌補了ArrayList增刪較慢的問題,但在查找方面又遜色於ArrayList,因此在使用時須要根據場景靈活選擇。對於這兩個頻繁使用的集合類,掌握它們的源碼並正確使用,可讓咱們的代碼更高效。java

LinkedList既實現了List,又實現了Deque,前者使它可以像使用ArrayList同樣使用,後者又使它可以承擔隊列的職責。LinkedList內部結構是一個雙向鏈表,咱們在分析ArrayDeque時提到過這個概念,就是擴充單鏈表的指針域,增長一個指向前一個元素的指針previousnode

AbstractSequentialList

AbstractSequentialListLinkedList的父級,它繼承自AbstractList,而且是一個抽象類,它主要爲順序表的鏈式實現提供一個骨架:dom

This class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "sequential access" data store (such as a linked list). For random access data (such as an array), AbstractList should be used in preference to this class.ide

意思是它的主要做用是提供一個實現List接口的骨架,來減小咱們實現基於鏈式存儲的實現類時所需的工做量。AbstractSequentialList並無作不少特殊的事情,其中最主要的是提供一個方法的默認實現,並將如下方法抽象,以期有更符合場景的實現:函數

public abstract ListIterator<E> listIterator(int index);

其餘一些方法的實現都利用了這個listIterator方法,咱們再也不一一查看了。下面咱們分析LinkedList的實現性能

LinkedList的結構

LinkedList的繼承結構以下所示:學習

file

能夠看到,LinkedList也實現了Cloneablejava.io.Serializable等方法,借鑑於ArrayList的經驗,咱們能夠想到它的Clone也是淺克隆,在序列化方法也採用了一樣的方式,咱們就再也不贅述了。ui

構造方法與成員變量

數據單元Node

在介紹鏈表結構時提到過,其數據單元分爲數據域和指針域,分別存儲數據和指向下一個元素的位置,在java中只要定義一個實體類就能夠解決了。this

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成員變量主要有三個,並且其意義清晰可見。指針

// 記錄當前鏈表的長度
transient int size = 0;

// 第一個節點
transient Node<E> first;

// 最後一個節點
transient Node<E> last;

構造函數

由於鏈表沒有長度方面的問題,因此也不會涉及到擴容等問題,其構造函數也十分簡潔了。

public LinkedList() {
}

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

一個默認的構造函數,什麼都沒有作,一個是用其餘集合初始化,調用了一下addAll方法。addAll方法咱們就再也不分析了,它應該是和添加一個元素的方法是一致的。

重要方法

LinkedList既繼承了List,又繼承了Deque,那它必然有一堆addremoveaddFirstaddLast等方法。這些方法的含義也相差不大,實現也是相似的,所以LinkedList又提取了新的方法,來簡化這些問題。咱們看看這些不對外的方法,以及它們是如何與上述函數對應的。

//將一個元素連接到首位
private void linkFirst(E e) {
    //先將原鏈表存起來
    final Node<E> f = first;
    //定義一個新節點,其next指向原來的first
    final Node<E> newNode = new Node<>(null, e, f);
    //將first指向新建的節點
    first = newNode;
    //原鏈表爲空表
    if (f == null)
        //把last也指向新建的節點,如今first與last都指向了它
        last = newNode;
    else
        //把原鏈表掛載在新建節點,也就是如今的first以後
        f.prev = newNode;
    size++;
    modCount++;
}

//與linkFirst相似
void linkLast(E e) {
    //...
}

 //在某個非空節點以前添加元素
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //先把succ節點的前置節點存起來
    final Node<E> pred = succ.prev;
    //新節點插在pred與succ之間
    final Node<E> newNode = new Node<>(pred, e, succ);
    //succ的prev指針移到新節點
    succ.prev = newNode;
    //前置節點爲空
    if (pred == null)
        //說明插入到了首位
        first = newNode;
    else
        //把前置節點的next指針也指向新建的節點
        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;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

private E unlinkLast(Node<E> l) {
    //...
}

//刪除一個指定的節點
E unlink(Node<E> x) {
    //...
}

能夠看到,LinkedList提供了一系列方法用來插入和刪除,可是卻沒有再實現一個方法來進行查詢,由於對鏈表的查詢是比較慢的,因此它是經過另外的方法來實現的,咱們看一下:

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

//能夠說盡力了
Node<E> node(int index) {
    // assert isElementIndex(index);
    
    //size>>1就是取一半的意思
    //折半,將遍歷次數減小一半
    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方法,須要遍歷
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

//也可能須要遍歷
public void add(int index, E element) {
    checkPositionIndex(index);

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

//也要遍歷
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

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

public E element() {
    return getFirst();
}

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

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

public boolean offer(E e) {
    return add(e);
}

public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

//...

總結

LinkedList很是適合大量數據的插入與刪除,但其對處於中間位置的元素,不管是增刪仍是改查都須要折半遍歷,這在數據量大時會十分影響性能。在使用時,儘可能不要涉及查詢與在中間插入數據,另外若是要遍歷,也最好使用foreach,也就是Iterator提供的方式。


【感謝您能看完,若是可以幫到您,麻煩點個贊~】

更多經驗技術歡迎前來共同窗習交流: 一點課堂-爲夢想而奮鬥的在線學習平臺 http://www.yidiankt.com/

![關注公衆號,回覆「1」免費領取-【java核心知識點】] file

QQ討論羣:616683098

QQ:3184402434

想要深刻學習的同窗們能夠加我QQ一塊兒學習討論~還有全套資源分享,經驗探討,等你哦! 在這裏插入圖片描述

相關文章
相關標籤/搜索