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