1. 程式人生 > >JDK中LinkedList的實現分析

JDK中LinkedList的實現分析

LinkedList

JDK中的LinkedList是繼承自AbstractSequentialList,並實現了List、Deque、Queue等介面,並支援拷貝和序列化。

public class LinkedList<E> extends AbstractSequentialList<E> implements
    List<E>, Deque<E>, Queue<E>, Cloneable, Serializable 

先來閱讀以下注釋,瞭解一下概況:

LinkedList是List的一個實現,基於雙向連結串列結構。所有的操作,包括新增,刪除,元素替換都是支援的。
支援所有的元素,包括null。
這個類在你需要類似佇列的行為的時候會用到。也會在你期望你的列表包含零個或者一個元素,但是又能擴充套件到更大數量的元素的時候。一般來說,你在不需要使用佇列類似的功能的時候,最好使用ArrayList。

Link類是一個內部靜態類,包括前驅和後繼以及資料,實現了連結串列節點的功能。

LinkIterator是一個實現ListIterator介面的內部靜態類,實現了連結串列的遍歷功能。

ReverseLinkIterator是一個實現了Iterator介面的內部類,實現了逆序遍歷的功能。

LinkedList使用一個叫做voidLink的空節點記錄連結串列的頭指標和尾指標,voidLink.previous是尾指標,voidLink.next是頭指標。在沒有結點時,連結串列的頭尾都指向voidLink自身。

LinkedList包含以下幾個主要操作的函式add、addAll、contains、get、indexOf、remove、push、pop、set等。下面一一來看看它們的實現。

首先是add,函式實現如下:

public void add(int location, E object) {
    if (location >= 0 && location <= size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        Link<E> previous = link.previous;
        Link<E> newLink = new Link<E>(object, previous, link);
        previous.next = newLink;
        link.previous = newLink;
        size++;
        modCount++;
    } else {
        throw new IndexOutOfBoundsException();
    }
}

add函式可以實現在指定位置新增指定的物件資料。如果位置超出範圍,會丟擲IndexOutOfBoundsException異常。
如果插入的位置location在連結串列的前半部分,那麼就從前面開始遍歷到該位置;如果location在連結串列的後半部分,就從連結串列的尾部開始往前遍歷到該位置。LinkedList之後通過該物件資料建立一個節點,並將節點插入到連結串列中,並更新前驅和後繼結點指標,以及連結串列長度。

不帶插入位置引數location的add函式則直接將物件資料插入到連結串列尾部。之後更新voidLink的連結串列的頭尾指標。

addAll函式將一個Collection集合中的所有物件資料都插入到指定位置,實現過程與add函式基本一致,只不過是連續插入多個元素。

addFirst函式則是建立新結點並設定前驅後繼,然後更新頭指標,更新原來頭指標的前驅,程式碼如下:

public void addFirst(E object) {
    addFirstImpl(object);
}

private boolean addFirstImpl(E object) {
    Link<E> oldFirst = voidLink.next;
    Link<E> newLink = new Link<E>(object, voidLink, oldFirst);
    voidLink.next = newLink;
    oldFirst.previous = newLink;
    size++;
    modCount++;
    return true;
}

addLast函式與addFirst函式實現類似。

contains函式用於搜尋連結串列是否包含某個指定的物件。首先判斷該物件是否為空,如果不為空,則從前向後遍歷該連結串列進行元素對比查詢,直到找到元素或者連結串列遍歷完為止;如果元素為空,則遍歷連結串列查詢第一個為空的結點。

get函式用於返回指定位置的結點資料。如果位置在連結串列長度允許範圍之內,則根據位置在前半部分或者後半部分的情況,從頭指標往後或從尾指標往前進行查詢。如果位置超出範圍,丟擲IndexOutOfBoundsException異常。

indexOf函式用於查詢指定物件資料在連結串列中的位置。實現方法是從前往後遍歷連結串列進行物件查詢,直到找到物件資料或者遍歷完連結串列。如果找到就返回位置,否則返回-1。
lastIndexOf函式與indexOf類似,但是不一樣的是它返回的是最後一次出現的位置。因此,遍歷方向需要變化,即變成從後往前進行連結串列的遍歷。

remove函式用於刪除指定位置的結點。先判斷位置在前半部分還是後半部分來確定遍歷方向,然後更新結點指標即可。另外該函式需要返回該位置的物件資料。如果位置超出範圍,也會丟擲IndexOutOfBoundsException異常。

public E remove(int location) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        Link<E> previous = link.previous;
        Link<E> next = link.next;
        previous.next = next;
        next.previous = previous;
        size--;
        modCount++;
        return link.data;
    }
    throw new IndexOutOfBoundsException();
}

另外pop與push是實現了棧的功能,這兩個函式是通過刪除隊首元素和在隊尾新增元素實現的。

set函式用於替換指定位置的結點中的物件資料。只需遍歷到位置並替換資料即可。

    public E set(int location, E object) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        E result = link.data;
        link.data = object;
        return result;
    }
    throw new IndexOutOfBoundsException();
}