LinkedList

Base:JDK1.8

一、LinkedList

LinkedList 也是一個比較常見的數據結構,鏈表。在C/C++ 中,鏈表也是一個典型的線性結構,鏈表分爲單向跟雙向的兩種鏈表。在java裏面的LinkedList是一個雙向的鏈表。java

鏈表最好的好處就在於來一個數據加一個長度,沒有多餘的冗餘, 也是支持存儲各類對象的(最好使用泛型)。node

二、繼承的類and實現的接口

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

2.一、AbstractSequentialList

2.二、List

跟ArrayList實現的同一個接口,所以就不貼了。數組

2.三、Dequen

deque 即雙端隊列。是一種具備隊列和棧的性質的數據結構。雙端隊列中的元素能夠從兩端彈出,其限定插入和刪除操做在表的兩端進行。數據結構

這個後續學隊列再去詳細的介紹。函數

2.四、Cloneable、java.io.Serializable

跟ArrayList同樣,都是支持克隆和序列化的。this

三、LinkedList中的成員變量、數據結構、經常使用方法

3.一、成員變量

transient int size = 0;

用來記錄 鏈表的大小,一樣是transient的。spa

transient Node<E> first;

表示頭結點。code

transient Node<E> last;

表示最後一個節點。對象

3.二、數據結構

由於鏈表也是一個典型的線性結構,跟數組類似,可是也有不一樣,由於鏈表在邏輯地址(咱們腦海中)是一個連續的地址,而在物理地址(電腦內存中)中卻不是連續的一塊,而是一個點,一個點的,這個節點持有上個節點,和下個節點的引用,如圖:繼承

 

由於邏輯地址並不要求是一塊內存,所以鏈表在這方面仍是要優於數組的。

可是正是不須要內存連續,所以要額外的須要持有上一個節點和下一個節點的引用。單個內存的開銷變大。

 

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中的內部類,Node ,每add一個數據就會有一個Node對象,每個節點都有先後兩個節點的引用,這就造成了一個雙向鏈表。

跟ArrayList 相同的,Node 節點也是 transient 的修飾的,所以,後面也會重寫private void writeObject(java.io.ObjectOutputStream s) 和 private void readObject(java.io.ObjectInputStream s) 這兩個方法。目的跟ArrayList應該是同樣的。

3.3 經常使用重要方法

3.3.1 構造方法

3.3.1.1 無參構造

/**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

不解釋。

3.3.1.2 參數爲   Collection 的構造方法

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

這個方法能夠直接將已經存在的Collection 轉換爲一個LinkList 包括ArrayList 還有其餘 子類。

----------------------

在這裏發現,LinkedList的構造方法是沒有指定長度的構造方法,這是跟數組的一個比較 重要的區別(這不是廢話嗎。。。。。。)

---------------------

3.3.2 add

3.2.2.1 public boolean add(E e)

public boolean add(E e) {
         //調用另一個方法
        linkLast(e);
        return true;
    }

因爲add方法內部是調用的另一個方法,所以貼出另外的方法。

/**
     * Links e as last element.
     */
    void linkLast(E e) {
        //last 是記錄的最後一個元素
        final Node<E> l = last;
        //new 一個Node 的節點
        final Node<E> newNode = new Node<>(l, e, null);
        //last更新爲新生成的
        last = newNode;
        //若是last爲null證實這是第一次添加,那麼他就是第一個,也是最後一個。
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        //大小+1
        size++;
        //fail-fast 機制
        modCount++;
    }

有這個方法名字就能夠很直白的看出來,是添到了鏈表的尾部。

其中的 new Node<>(l,e,null) 能夠去看上面的 貼出來的Node的構造方法,

Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }

以前的last設爲 新的 prev,next 設置爲null   item 設置爲存儲的數據。

3.2.2.2 public void add(int index, E element)

public void add(int index, E element) {
        //下標檢測,是否超出
        checkPositionIndex(index);
        //若是位置正好等於 size ,就直接插入到最後
        if (index == size)
            linkLast(element);
        else
            //插入到指定位置
            linkBefore(element, node(index));
    }

因爲鏈表也是一個線性結構,所以也支持夠插入到指定位置。

void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //插入以前的index 位置上的元素的 前一個元素
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        //將原 index 位置上的元素的前一個 設置爲新的節點 
        succ.prev = newNode;
        if (pred == null)
            //若是此節點的前一個是null,意味着插入的是首個位置
            first = newNode;
        else
            //前 index 位置的元素的前一個的 下一個引用,設置爲新節點
            pred.next = newNode;
        size++;
        modCount++;
    }

畫一個圖應該是更好理解。

------------------------------------------------

靈魂畫手:

大致的分爲三步, 若是插入的位置是 0 ,跟這個差很少。

------------------------------------------------------

3.2.2.3 其餘的add

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

這個從名字就很好看出來,就是添加到位置 0。

不過爲何不用add(0,e),而是再寫一個方法呢?

----------------------------------------------------------

public void addLast(E e)

public void addLast(E e) {
        linkLast(e);
    }
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具體位置插入,爲何不用這個方法,而是另外再寫呢?

public boolean addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

 

 public boolean addAll(int index, Collection<? extends E> 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;
    }

這兩個 方法是將 Collection 的某個集合裏面的所有數據放入到 LinkedList 中,感受用的不是不少,問的也不會不少,我也沒注意過,不看了。

--------------------------------------------------------

3.3.3 remove

 public E remove(int index)

public E remove(int index) {
        //邊界檢測
        checkElementIndex(index);
          //執行刪除的函數
        return unlink(node(index));
    }
Node<E> node(int index) {
        // assert isElementIndex(index);
          //若是index 是小於 size/2的 就從前找
        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) 的方法,能夠看出他是返回了 index位置上的 node的引用,由於鏈表在內存中是不連續的,因此只能經過遍歷鏈表去查找 對應的數據,

這個判斷 index<(seze>>1) 的思想是 二分法的思想,將整個鏈表分爲2份,看從哪邊近,就從哪邊開始,這也是雙鏈表的優勢,若是是單鏈表,就只能從前日後遍歷了。

E unlink(Node<E> x) {
        // assert x != null;
         //首先記錄三個節點,當前的,前一個,後一個,每一個對結構有調整的(add,remove )
         // 通常第一步 都是記錄這三個
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //若是前一個是null,就意味着刪除的是first節點
        if (prev == null) {
            //直接將 first指向 第二個
            first = next;
        } else {
            //若是不是first元素,將要刪除的前一個元素的 next 指向 當前元素的下一個
            prev.next = next;
           //將當前的前一個元素置空。
            x.prev = null;
        }
        //意味刪除的是最後一個節點
        if (next == null) {
           //last 前移一個
            last = prev;
        } else {
           // 不然將下一個節點 的 前一個引用 設爲 當前節點的前一個
            next.prev = prev;
           //將 當前節點的 下一個置空 
            x.next = null;
        }
       //數據置空
        x.item = null;
        size--;
        modCount++;
        return element;
    }

---------------------------------

來個圖:

 

 

忽然感受本身頗有畫圖天賦。。。。23333333

-------------------------------------------

public boolean remove(Object o)

public boolean remove(Object o) {
        if (o == null) {
            //若是 須要刪除的對象是null 
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
          //對象不是null 就是用 equals方法 
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

這個其實只是分了 null和非null 兩種狀況,null 使用== ,非null 使用 equals 方法進行比較。

其中調用的刪除的方法 仍是 unlink(x) 。

時刻記得,鏈表雖然是一個線性表,可是沒法像 數組那樣,直接下標偏移量 找到刪除的元素,這個必須須要進行一個 遍歷,將整個鏈表 遍歷完成。

一樣的,這裏刪除一樣要避免刪除的時候,你想刪除的若是是int 類型的數據,切記要轉換爲 包裝類,不然調用的方法是remve(int)。

---------------------------------------------

其餘的remove方法

public E removeFirst()

public E removeLast()

----------------------------------------------

3.3.4 get

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

整個get方法不多,判斷是否越界,而後返回 index 位置節點的 item 。

--------------------

其餘 get方法

public E getFirst()

public E getLast()

------------------------

其餘相似 get 方法

public E peek()

/**
     * Retrieves, but does not remove, the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
 public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

public E element()

/**
     * Retrieves, but does not remove, the head (first element) of this list.
     *
     * @return the head of this list
     * @throws NoSuchElementException if this list is empty
     * @since 1.5
     */
    public E element() {
        return getFirst();
    }

 

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

 public E poll()

/**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

這三個方法都是返回first  ,不過第一、3方法是 若是 first 是 null 就返回null ,第2個方法內部調用的是getFirst(),這個方法:若是是空,就 拋出異常。

一、2兩個方法只是返回,不刪除元素,3方法是返回切刪除了元素。

 

這裏面有不少相似的方法,就不貼了,貼了浪費流量。

3.2.5 contains

public boolean contains(Object o)

public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

這個是是否包含 某個元素。須要傳入的是對象。

3.3 其餘方法

 private void writeObject(java.io.ObjectOutputStream s)

private void readObject(java.io.ObjectInputStream s)

具體代碼不貼了,感受這個LinkedList寫的有點囉嗦了。

public Object[] toArray()

public <T> T[] toArray(T[] a)

-----------------------------------------------------------------------------

不保證代碼徹底正確,也不保證代碼是最優。

僅僅是根據本身的理解,寫出來直觀的代碼,方便理解。

錯誤請指出,感激涕零!

相關文章
相關標籤/搜索