1. 程式人生 > >【Java集合】試讀LinkedList源碼

【Java集合】試讀LinkedList源碼

是否 包含成員 sta pub tex .com 生成 否則 class

技術分享

LinkedList的本質是雙向鏈表。
(01) LinkedList繼承於AbstractSequentialList,並且實現了Dequeue接口。
(02) LinkedList包含兩個重要的成員:headersize
  header是雙向鏈表的表頭,它是雙向鏈表節點所對應的類Entry的實例。Entry中包含成員變量: previous, next, element。其中,previous是該節點的上一個節點,next是該節點的下一個節點,element是該節點所包含的值。
  size是雙向鏈表中節點的個數。

(前面照舊是復制粘貼的圖和文字,大家大概理解一下,下面進入正題)

為了理解上面的概念,首先我們來看一下核心類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表示的是結點,結點裏面有三個元素:

數據,前驅和後繼。

其中數據可是任意類型,前驅和後繼同樣是結點。

我們可以想象一個雙向鏈表依次一共有A,B,C三個結點,他們的數據分別為a,b,c。那麽:

A的前驅為null,後繼為B,數據為a。

B的前驅為A,後繼為C,數據為b。

C的前驅為B,後繼為null,數據為c。

接下來我們來看一下構造函數和類變量

    //集合元素個數
    transient int size = 0;

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

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

    //輸入為空的構造函數
    public LinkedList() {
    }

    //直接傳入一個Collection放入LinkedList中的構造器,
    public
LinkedList(Collection<? extends E> c) { //調用無參的構造期 this(); addAll(c); }

類變量分別是List中數據的個數,第一個結點和最後一個結點。

構造函數有兩個:一個是空的構造函數,一個是傳入一個Collection來生成LinkedList。

我們來具體看一下這個addAll方法

 //將指定集合c中所有的元素,按照其叠代器返回的順序全部追加到集合的結尾。
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    //將指定集合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;
        //pred是predecessor前置節點,succ是succeed 後置節點,請大家學好英語(笑)
        Node<E> pred, succ;
        if (index == size) {
            //新增節點在最後一個
            succ = null;
            pred = last;
        } else {
            //新增結點在index處
            succ = node(index);
            pred = succ.prev;
        }
        //前驅節點不為null的情況下,循環生成新節點,把前任節點作為新節點的前驅,數組裏的數作為節點的值,後繼置為空
        //然後把新節點作為前驅的後繼,之後把新節點作為前驅,繼續循環執行
        //可能你這個時候會有疑問,那不是沒有制定後繼?並不是的,後繼是在你的新節點變為前驅後,由 pred.next = newNode;這一句指定的。
        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;
        }
        //後繼為空,則最後一個就是前驅(也就是前面最後一句指定為前驅的newNode)
        //後繼不為空的話,則把後繼作為前驅(就是前面最後一句指定為前驅的newNode)的後繼,前驅作為後繼的前驅
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }
        //列表裏的數增加
        size += numNew;
        //這個用來判斷叠代器的fast-fail的,具體見我的前一篇ArrayList的那篇博文
        modCount++;
        return true;
     }

整個把Collection變為LinkedList的過程寫的比較詳細了,不再贅述。

現在我們隨便看一些常用的方法,比如說獲取第一個結點的值,我們發現會有getFirst()和peekFirst()這樣兩個方法;同樣的獲取最後一個結點的值,我們發現會有getLast()和peekLast()兩個方法。那麽為何會有兩種呢?

我們看一下源碼:

//獲取第一個結點的值
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

//獲取的第一個結點的值
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
 }

我們可以看出來,前者如果結點為空會報錯,後者如果結點為空則會返回null。

以下對第一個結點和最後一個結點的操作:

        第一個結點(頭部)                 最後一個結點(尾部)
        拋出異常        特殊值            拋出異常        特殊值
插入    addFirst(e)    offerFirst(e)    addLast(e)      offerLast(e)
移除    removeFirst()  pollFirst()      removeLast()    pollLast()
檢查    getFirst()     peekFirst()      getLast()        peekLast()

左邊的操作遇到異常會拋出異常,右邊的操作遇到異常會返回特殊值。

由於LinkedLIst分別實現了隊列和棧的接口,以下也是對第一個結點和最後一個結點的操作

當作為隊列時,下表的方法等價:

隊列方法       等效方法
add(e)        addLast(e)
offer(e)      offerLast(e)
remove()      removeFirst()
poll()        pollFirst()
element()     getFirst()
peek()        peekFirst()

當作為棧時下表的方法等價:

棧方法        等效方法
push(e)      addFirst(e)
pop()        removeFirst()
peek()       peekFirst()

以上說的都是對第一個結點和最後一個結點的操作,接下來寫一下對中間結點的操作:

 //返回特定位置的結點的值
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    //替換特定位置的結點的值,返回舊的值
    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));
    }

裏面的具體操作如下:

   //獲取某個index的結點
    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;
        }
    }

//把輸入的數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++;
    }

    //把輸入的數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++;
    }

    //把輸入的數e作為新增在結點succ的前面的結點的值
    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)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

    //把非空的LinkedList的第一個節點unlinked(刪除)
    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;
    }

    //把非空的LinkedList的最後一個節點unlinked(刪除)
    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;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    ///把非空的LinkedList的某個節點unlinked(刪除)
    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;
    }

可以從上面看出來新增和刪除元素都是比較方便的。

還有兩個比較特殊的刪除方法:

    //刪除第一個出現的特定值
    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(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;
    }

它們的特殊之處在於,它們想要刪除的結點的數值也許有很多個,但是它們只會刪除第一個出現的或者是最後一個出現的。

然後我們看一下搜索元素的方法:

   //判斷是否包含某個特定的結點的值
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

//查找LinkedList中是否包含某個值,並返回第一個出現這個值的索引值,否則返回-1
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

    //反向查找LinkedList中是否包含某個值,並返回第一個出現這個值的索引值,否則返回-1
    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

可以看出來,搜索元素是比較麻煩的,必須要全部遍歷一遍。

最後我們看一下一些邊界值判斷的方法:

    //判斷某個索引值是否存在
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    //判斷這個索引是否超出了位置的邊界,這個和上面的有何區別?為何index是<=而不是<
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    //多種邊界異常的判斷
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

限於篇幅(Lan),其他方法就不一一介紹了。

總結

1.LinkedList的本質基於雙向鏈表實。

2.LinkedList在查找元素時,必須遍歷鏈表;在新增和刪除元素時,只要調整前後的引用就可以了。

3.LinkedList不是線程安全的,同樣擁有fast-fail機制。

【Java集合】試讀LinkedList源碼