【Java源碼分析】LinkedList源碼分析

類的定義如下

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
  1. 本質是一個雙向鏈表,實現了List和Deque接口。實現了所有List的操作,可以存儲所有類型的對象,包括NULL
  2. 非線程安全多線程調用的時候必須通過外部保證線程安全性,所有的添加或者刪除操作也必須保證線程安全性。通常的實現方法是通過對象鎖的形式保證線程安全性,或者通過Collections.synchronizedList實現,例如List list = Collections.synchronizedList(new LinkedList(...));
  3. 迭代器進行遍歷的時候同樣存在fail-fast現象,可以參考ArrayList分析中的描述
  4. 由於是AbstractSequentialList的子類,同時本質是雙向鏈表,所以只能順序訪問集合中的元素
  5. 和ArrayList一樣也是支持序列化操作的

成員變量

transient int size = 0; // 集合中對象個數
transient Node<E> first; // 指向第一個對象的指針
transient Node<E> last; // 指向最後一個對象的指針

構造函數有兩個,一個是無慘默認構造,一個是根據傳遞的集合類構造一個包含指定元素的鏈表。相對於ArrayList而言,LinkedList的構造要簡單很多,因爲不涉及到容量的變化

public LinkedList() {}

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

從頭尾添加對象

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

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

其中所用的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;
    }
}

該類的主要就是一個構造函數,直接指定前驅和後繼,new的時候就將指針鏈接完成。相應的還有unlinkFirst和unlinkLast操作,以及linkBefore和linkAfter操作,以及unlink(Node n)操作,操作都是基本的雙鏈表操作。這些操作的共同點在於操作都是針對集合中的對象而言,所以只需要修改鏈表就可以了。

刪除操作(和之前不同之處在於這裏給定的參數並不是集合中的對象而是一個值與集合中對象的節點的item值相同)

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

和前面的方法相比,這種知道值刪除集合對象的操作就是一個遍歷查找的過程,由於是鏈表實現,所以就是一個單鏈表或者雙鏈表的遍歷加上修改指針的操作

清空集合操作

public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

這個函數重點部分在註釋中已經說明了,對於鏈表類釋放的時候節點間的指針並不是必須要釋放的,但是釋放了可以幫助GC有效的回收,也可以保證徹底的釋放內存,即使是存在迭代器(由於clear()操作更改了LinkedList的結構,所以指向它的迭代器會因爲ConcurrentModification而fail-fast)

返回指定下標的節點

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是靠左半邊還是右半邊

取首節點的操作

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

三個方法都可以取首節點的item值,但是前兩個只是取出並不刪除,但是第三個函數返回首節點item並且刪除首節點

添加節點

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

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

public boolean offerLast(E e) {
    addLast(e);
    return true;
}

第一個添加就是在鏈表的尾部添加,後面兩個添加操作一個是添加到鏈表的首部一個是添加到尾部,這兩個方法屬於是雙端隊列Dequeue的操作

兩個很類似棧的方法

public void push(E e) {
    addFirst(e);
}

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

Push和Pop都是針對鏈表的首部元素而言,所以LinkedList不僅可以當做一個雙端隊列使用,也可以當做一個棧來使用

和ArrayList一樣,LinkedList內部類迭代器也同樣需要記錄modCount,所以也會出現fail-fast

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned = null;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;
.....
}

扮演集合Collection與數組Array之間轉換橋樑的函數

public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}

@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    if (a.length > size)
        a[size] = null;

    return a;
}

第二個是泛型版本,將LinkedList內容轉換爲指定運行時類型的一個數組存儲起來。這個方法比第一個方法的優勢在於可以動態確定類型,而且可以減少allocation的代價

序列化和反序列化(狀態的保存和讀取)

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // Write out any hidden serialization magic
    s.defaultWriteObject();

    // Write out size
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (Node<E> x = first; x != null; x = x.next)
        s.writeObject(x.item);
}


@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();

    // Read in size
    int size = s.readInt();

    // Read in all elements in the proper order.
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject());
}

同樣需要注意寫入順序必須和讀取順序對應。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章