LinkedList詳解-源碼分析

LinkedList詳解-源碼分析

LinkedList是List接口的第二個具體的實現類,第一個是ArrayList,前面一篇文章已經總結過了,下面咱們來結合源碼,學習LinkedList。java

  • 基於雙向鏈表實現node

  • 便於插入和刪除,不便於遍歷數組

  • 非線程安全安全

  • 有序(鏈表維護順序)數據結構

  • ...源碼分析

上面是LinkedList的一些特性。學習

1. LinkedList類聲明

源碼以下所示:this

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

初步分析:線程

  • 繼承了AbstractSequentialList抽象類
  • 實現了List、Deque、Cloneable、Serializable接口

思考:code

  • List、Cloneable、Serializable接口在上一篇ArrayList詳解裏已經分析過了,這個Deque接口是幹嗎的呢?

咳咳,先谷歌一下,發現Deque的意思是雙端隊列,這裏已經能夠看出LinkedList是基於雙向鏈表的一些端倪了,帶着這點疑問,咱們繼續往下看。

2. 成員變量

源碼以下所示:

transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

private static final long serialVersionUID = 876323262645176354L;

比ArrayList的成員變量少了好幾個呢。

初步分析:

  • size依然是集合內的元素個數
  • transient關鍵字標識變量不會被序列化
  • Node是節點的意思,具體代碼是什麼樣的?

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的代碼,Node是LinkedList的靜態內部類,仍是在LinkedList.class文件內部的

分析:

  • 聲明瞭三個成員變量
  • 分別表示當前元素,下一個節點,上一個節點

也就是說,LinkedList的每個元素都是一個Node,而每個Node都儲存了三部份內容,由此也就證明了LinkedList是基於雙向鏈表的。

3. 構造方法

源碼以下所示:

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

分析:

  • LinkedList提供了兩個構造方法
  • 分別是對應無參構造和傳入Collection子類進行構造

能夠發現,相對於ArrayList,LinkedList類並無指定容量的構造,這是爲何呢?

思考:

1. 這就是ArrayList和LinkedList底層依賴不一樣有關係,ArrayList底層是數組,LinkedList底層是雙向鏈表。數組初始化是須要聲明長度的,鏈表則不須要。

2. 傳入子類進行構造時,也是調用了無參構造方法,再調用addAll()方法,將全部元素添加進去

4. 經常使用方法分析

addFirst(E e)

源碼以下所示:

public void addFirst(E e) {
        linkFirst(e);
    }
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++;
    }

addFirst()方法是在鏈表頭部插入一個元素,分析以下:

  • 先獲取到原來的頭節點,賦值給f
  • 建立一個新的節點newNode,該節點的next節點爲f
  • newNode賦值給first
  • 若是原來的頭節點是null的話,說明此時鏈表是空的,添加的是第一個元素,則將newNode也賦值給last節點
  • 若是原來的頭節點不是null,那麼將原來的頭節點fpreNode設置爲newNode
  • 鏈表長度加1,鏈表修改次數加1

add(E e)

源碼以下:

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方法是否是很眼熟?和前面的linkeFirst基本一致噢
  • 不一樣之處僅在於,linkFirst是對對頭節點進行變動,而linkLast是對尾節點進行變動
  • 此處再也不贅述

get(int index)

源碼以下所示:

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

get()方法內隱藏着LinkedList不便於進行遍歷的真相!必定要搞明白哦。

分析:

  • 第一步先確認index是否在正確的範圍內,範圍爲(0~size)
  • 第二步調用node方法返回對應索引位置的節點元素

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和鏈表長度的1/2的大小
  • 若是index小於鏈表長度的1/2,那麼就會從頭節點向index位置進行遍歷,直到獲取到相應節點並返回該節點
  • 若是index大於鏈表長度的1/2,那麼從尾節點向index位置進行遍歷,直到獲取到相應節點並返回該節點

能夠看出,當你訪問的元素越靠近鏈表的中間,那麼獲取該元素所花費的時間就會越長,因此LinkedList在遍歷上是比較慢的,鏈表自己是不支持任意性訪問的,雖然LinkedList的get()方法能夠讀到相應元素,可是效率很低,不建議使用。

remove(Object o)

源碼以下所示:

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時,使用==進行元素內容的判斷,而後調用unlink方法
  • 元素不爲null時,使用equals方法進行判斷兩個元素是否相同,而後調用unlink方法

unlink方法源碼以下所示:

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方法源代碼有點長啊,容我慢慢道來:

  • 定義三個變量分別接收傳入節點x的內容、上一個節點、下一個節點
  • 若是節點x的上一個節點爲null的話,說明x節點是頭節點,那麼就將x節點的下一個節點賦值給頭節點
  • 若是節點x不是頭節點,則將x節點的下一個節點賦值給上一個節點的next節點,並將x節點的上一個節點置爲null
  • 經上面兩步,已經完成了x節點和上一個節點的斷開,以及下一個節點和x節點的上一個節點的連接
  • 若是x節點的下一個節點爲null,說明x節點是尾節點,那麼就將x節點的上一個節點賦值給last節點
  • 若是x節點不是尾節點,那麼將x節點的上一個節點,賦值給下一個節點的prev節點,並將x節點的下一個節點置爲null
  • 通過上面幾步以後,x節點就已經從鏈表中移除了
  • 而後將x節點的節點內容置爲null,鏈表長度減1,修改長度記錄加1
  • 返回刪除節點的內容element

removeFirst()

源碼以下所示:

public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

分析:

  • 獲取鏈表頭節點,賦值給f
  • 若是f等於null,說明此時鏈表是空的,拋出異常
  • 若是f不等於null,調用unlinkFirst方法,傳入f

unlinkFirst()方法源碼以下所示:

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

分析:

  • 獲取f節點內容,賦值給element變量,獲取fnext節點賦值給變量next
  • f節點內容,f節點的next節點,均賦值爲null,等待GC回收
  • next節點賦值給first
  • 若是nextnull的話,說明此時鏈表爲空了,因此將尾節點last也賦值爲null
  • 不然,將next節點的prev成員變量賦值爲null
  • 鏈表長度減1,修改記錄數加1
  • 返回被移除的元素

5. 其餘方法概述

LinkedList能夠做爲FIFO(First In First Out)的隊列,也就是先進先出的隊列使用,如下是關於隊列的操做。

//獲取隊列的第一個元素,若是爲null會返回null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
	//獲取隊列的第一個元素,若是爲null會拋出異常
    public E element() {
        return getFirst();
    }
	//獲取隊列的第一個元素,若是爲null會返回null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
	//獲取隊列的第一個元素,若是爲null會拋出異常.
    public E remove() {
        return removeFirst();
    }
	//將元素添加到隊列尾部
    public boolean offer(E e) {
        return add(e);
    }

LinkedList也能夠做爲棧使用,棧的特性是LIFO(Last In First Out),也就是後進先出。 添加和刪除元素都只操做隊列的首節點便可。

源碼以下:

public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
	
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }
 	
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
	
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }
	
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
	
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }
	
    public void push(E e) {
        addFirst(e);
    }
	
    public E pop() {
        return removeFirst();
    }
	
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

	
	public boolean removeLastOccurrence(Object o) {
     	
        if (o == null) {
			for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    //調用unlink方法刪除指定節點
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

6. 總結

LinkedList相對於ArrayList而言,源碼並無很複雜,從源碼中咱們得知了如下相關信息:

  • LinkedList是基於雙向鏈表實現的,即每個節點都保存了上一個節點和下一個節點的信息
  • LinkedList根據索引獲取元素效率低的緣由是由於它須要一個節點一個節點的遍歷,獲取首節點和尾節點很快
  • LinkedList實現了Deque接口,具備雙向隊列的性質,能夠實現數據結構中的堆棧。
  • ...

知之爲知之,不知爲不知,是知也。

相關文章
相關標籤/搜索