JDK1.8源碼學習-LinkedList

  JDK1.8源碼學習-LinkedListjava

目錄node

1、LinkedList簡介數組

LinkedList是一個繼承於AbstractSequentialList的雙向鏈表,是能夠在任意位置進行插入和移除操做的有序序列。數據結構

LinkedList基於鏈表實現,在存儲元素的過程當中,無需像ArrayList那樣進行擴容,可是有得必有失,LinkedList存儲元素的節點須要額外的空間存儲前驅和後繼的引用。此外,LinkedList在鏈表頭部和尾部插入效率比較高,可是在指定位置進行插入操做時,效率通常。緣由是在指定位置插入須要定位到該位置處的節點,此操做的時間複雜度爲O(N)。函數

2、LinkedList工做原理源碼分析

在LinkedList中,每個元素都是Node存儲,Node擁有一個存儲值的item和一個前驅prev和一個後繼next,源碼以下:學習

// 鏈表結構
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;
    }
}

3、LinkedList源碼分析this

3.一、繼承關係分析spa

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

LinkedList繼承自AbstractSequentialList,實現了List、Deque、Cloneable、Serializable接口,其中List接口中定義了一些隊列的基本的操做,Deque接口可以使LinkedList看成雙端隊列使用,Cloneable接口可使LinkedList調用clone()方法,進行淺層次的拷貝,Serializable接口可使LinkedList實現序列化。指針

3.二、成員變量分析

//實現Serilizable接口時,將不須要序列化的屬性前添加關鍵字transient,
//序列化對象的時候,這個屬性就不會序列化到指定的目的地中。
      transient int size = 0;
      //指向首節點
      transient Node<E> first;
       //指向最後一個節點
      transient Node<E> last;

3.三、構造函數分析

3.3.1 無參構造函數

    //無參的構造函數
    public LinkedList() {
    }

3.3.2 傳入集合c的構造函數

    public LinkedList(Collection<? extends E> c) {
            this();
            // 將集合添加到鏈表中去
            addAll(c);
        }

    public boolean addAll(Collection<? extends E> c) {
            // 從鏈表尾巴開始添加集合中的元素
            return addAll(size, c);
        }

    public boolean addAll(int index, Collection<? extends E> c) {
        // 1.添加位置的下標的合理性檢查
        checkPositionIndex(index);

        // 2.將集合轉換爲Object[]數組對象
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        // 3.獲得插入位置的前繼節點和後繼節點
        Node<E> pred, succ;
        if (index == size) {
            // 從尾部添加的狀況:前繼節點是原來的last節點;後繼節點是null
            succ = null;
            pred = last;
        } else {
           // 從指定位置(非尾部)添加的狀況:前繼節點就是index位置的節點,後繼節點是index位置的節點的前一個節點
            succ = node(index);
            pred = succ.prev;
        }

        // 4.遍歷數據,將數據插入
        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置爲最後一個插入的元素
            last = pred;
        } else {
            // 若是不是從尾部插入的,則把尾部的數據和以前的節點連起來
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;  // 鏈表大小+num
        modCount++;  // 修改次數加1
        return true;
    }

3.四、add()方法分析

LinkedList中的add方法有兩個,一個是add(E e)方法,一個是add(int index, E element)方法。

3.4.一、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節點也爲須要插入的節點。(也就是說:該節點既是頭節點first也是尾節點last) first = newNode; else // 不是空鏈表的狀況:將原來的尾部節點(如今是倒數第二個節點)的next指向須要插入的節點 l.next = newNode; size++; // 更新鏈表大小和修改次數,插入完畢 modCount++; }

3.4.二、add(int index,E element)方法

    // 做用:在指定位置添加元素
    public void add(int index, E element) {
        // 檢查插入位置的索引的合理性
        checkPositionIndex(index);

        if (index == size)
            // 插入的狀況是尾部插入的狀況:調用linkLast()。
            linkLast(element);
        else
            // 插入的狀況是非尾部插入的狀況(中間插入):linkBefore()見下面。
            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的前節點,後接點是succ節點
        succ.prev = newNode;  // 更新插入位置(succ)的前置節點爲新節點
        if (pred == null)
            // 若是pred爲null,說明該節點插入在頭節點以前,要重置first頭節點 
            first = newNode;
        else
            // 若是pred不爲null,那麼直接將pred的後繼指針指向newNode便可
            pred.next = newNode;
        size++;
        modCount++;
    }

3.五、get(int index)方法分析

    public E get(int index) {
        // 元素下表的合理性檢查
        checkElementIndex(index);
        // node(index)真正查詢匹配元素並返回
        return node(index).item;
    }

    // 做用:查詢指定位置元素並返回
    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;
        }

3.六、remove(int index)方法分析

    // 做用:移除指定位置的元素
    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; // 獲得指定節點的前繼節點

            // 若是prev爲null表示刪除是頭節點,不然就不是頭節點
            if (prev == null) {
                first = next;
            } else {
                prev.next = next;
                x.prev = null; // 置空需刪除的指定節點的前置節點(null)
            }

            // 若是next爲null,則表示刪除的是尾部節點,不然就不是尾部節點
            if (next == null) {
                last = prev;
            } else {
                next.prev = prev;
                x.next = null; //  置空需刪除的指定節點的後置節點
            }

            // 置空需刪除的指定節點的值
            x.item = null;
            size--; // 數量減1
            modCount++;
            return element;
        }

3.七、clear()方法分析

       // 清空鏈表
    public void clear() {// 進行for循環,進行逐條置空;直到最後一個元素
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        // 置空頭和尾爲null
        first = last = null;
        size = 0;
        modCount++;
    }

3.八、indexOf(Object o)方法分析

  // 返回列表中第一次出現o的位置,若不存在則返回-1
  public int indexOf(Object o) {
        int index = 0;
        // 若是元素爲null,進行以下循環判斷
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
        // 元素不爲null.進行以下循環判斷
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

 3.九、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; // 頭尾元素都是e
        else
            f.prev = newNode;  // 不然就更新原來的頭元素的prev爲新元素的地址引用
        size++;
        modCount++;
    }

3.十、addLast(E e)方法分析

    // 做用:在鏈表尾部添加元素e
   public void addLast(E e) {
        // 上面已講解過,參考上面。add()方法
        linkLast(e);
    }

3.十一、push(E e)方法分析

  // 做用:將元素添加到頭部(入棧)
    public void push(E e) {
        addFirst(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++;
    }

3.十二、getFirst()方法分析

    // 做用:獲得頭元素
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

3.1三、getLast()方法分析

    // 做用:獲得尾部元素
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

3.1四、peek()方法分析

    // 做用:返回頭元素,而且不刪除。若是不存在也不錯,返回null
   public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

3.1五、peekFirst()方法分析

    //  做用:返回頭元素,而且不刪除。若是不存在也不錯,返回null
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

3.1六、peekLast()方法分析

    // 做用:返回尾元素,若是爲null,則就返回null
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

3.1七、poll()方法分析

    // 做用:返回頭節點元素,並刪除頭節點。並將下一個節點設爲頭節點。
   public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

3.1八、pollFirst()方法分析

    // 做用:返回頭節點,並刪除頭節點,並將下一個節點設爲頭節點。
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

3.1九、pollLast()方法分析

    // 做用:返回尾節點,而且將尾節點刪除,並將尾節點的前一個節點置爲尾節點
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

3.20、pop()方法分析

    // 做用:刪除頭節點,若是頭結點爲null.則拋出異常(出棧)
    public E pop() {
        return removeFirst();
    }

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

4、利用LinkedList實現棧操做

public class Stack<T>
{
    private LinkedList<T> stack;
    
    //無參構造函數
    public Stack()
    {
        stack=new LinkedList<T>();
    }
    //構造一個包含指定collection中全部元素的棧
    public Stack(Collection<? extends T> c)
    {
        stack=new LinkedList<T>(c);
    }
    //入棧
    public void push(T t)
    {
        stack.addFirst(t);
    }
    //出棧
    public T pull()
    {
        return stack.remove();
    }
    //棧是否爲空
     boolean isEmpty()
     {
         return stack.isEmpty();
     }
     
     //打印棧元素
     public void display()
     {
         for(Object o:stack)
             System.out.println(o);
     }
}

5、LinkedList總結

ArrayList和LinkedList的大體區別:

1.ArrayList是基於動態數組的數據結構,LinkedList是基於鏈表的數據結構。

2.對於隨機訪問的get和set方法,ArrayList要優於LinkedList,由於LinkedList要移動指針。

3.數組遍歷的方式ArrayList推薦使用for循環,而LinkedList則推薦使用forearch,若是使用for循環,效率會很慢。

通常來講,對於新增和刪除操做add和remove,LinkedList比較佔優點,由於ArrayList要移動數據,可是這樣說是有一些問題的。

     LinkedList作插入、刪除的時候,慢在尋址,快在只須要改變先後的Node的引用地址;

    ArrayList作插入、刪除的時候,慢在數組元素的批量copy,快在尋址。

因此,若是待插入、刪除的元素是在數據結構的前半段尤爲是很是靠前的位置的時候,LinkedList的效率將大大快過ArrayList,由於ArrayList將批量copy大量的元素;越日後,對於LinkedList來講,由於它是雙向鏈表,因此在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差異,可是ArrayList因爲要批量copy的元素愈來愈少,操做速度上必然追上乃至超過LinkedList。

在實際中應該怎樣選擇呢?

  若是你十分肯定你插入、刪除的元素是在前半段,使用LinkedList,反之使用ArrayList。

       若是不肯定,建議使用LinkedList,由於LinkedList總體插入、刪除的執行效率比較穩定,沒有ArrayList這種越日後越快的狀況,且ArrayList可能會進行擴容,擴容是一件即消耗時間又消耗空間的操做。

相關文章
相關標籤/搜索