java.util.LinkedList學習筆記
概述
繼承結構
類描述
如繼承結構所示,LinkedList是兩個介面(List和Deque)的混合實現。其實現了List介面中所有的可選操作,並且LinkedList允許所有元素(包括null)的插入和訪問操作。LinkedList的所有操作均基於雙向連結串列實現,當根據下標對連結串列中的元素進行訪問,將通過從頭至尾或者從尾至頭遍歷連結串列中的所有元素,來到達指定位置。
LinkedList和ArrayList都不是執行緒安全的。當多個執行緒同時訪問一個連結串列時,如果有一個對連結串列的結構進行了修改,則必須在外部顯示的進行同步的處理。一般情況下,通過對包含本連結串列的物件進行同步處理。如果沒有這種型別的物件,則可以通過如下方式生成一個執行緒安全的連結串列:
List list = Collections.synchronizedList(new LinkedList(...));
該類中的iterator
和listIterator
返回的迭代器仍然採用快速失敗(fail-fast)機制:當連結串列由於呼叫除該迭代器的remove
和add
方法外,產生了結構行修改,該迭代器將丟擲一個同步修改異常(ConcurrentModificationException
)。因此,當發生同步修改時,迭代器將不會在未來的某個時間裡,返回潛在的髒陣列,或者產生不可預期的行為。
注意:快速失敗機制並不會保證方法生非同步併發修改時行為的正確性(我想到的情況是:在兩個程序進行列表結構修改操作的時間相同,且對方法中進行失敗機制校驗的方法呼叫時間也極其接近時,可能出現兩個程序均通過同步修改校驗, 且均對連結串列進行了結構行修改),這樣對於這兩個程序,仍然認為連結串列的是同步的,其實已經產生了髒資料。快速失敗機制只是在保證檢測到同步修改時,丟擲異常,證明髒資料存在的可能。因此,在編碼過程中最好不要依賴同步修改異常做邏輯處理,但快速失敗機制一般可用於除錯bug。
內部類介紹
資料節點類(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類為一個靜態內部類,其不需要外部物件即可初始化。由Node的結構及屬性可以看出,LinkedList的資料儲存結構與C
語言中雙向連結串列形式相同。每個節點除持有該節點的有效資料外,還需要持有指向上個節點和下個節點的指標,因此其單個節點佔用的空間高於ArrayList中採用陣列儲存的節點。
列表迭代器類(ListItr)
屬性介紹
private Node<E> lastReturned;
使用者最後一次呼叫next()
或者previous
返回的節點;
private Node<E> next;
使用者下次呼叫next()
返回的節點;
private int nextIndex;
使用者下次呼叫next()
返回的節點的下標值。
private int expectedModCount = modCount;
迭代器建立時,其持有的連結串列物件發生結構性修改的次數,主要用於併發修改的校驗;
構造方法
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
生成一個由連結串列特定位置開始的列表迭代器。當用戶傳入的index(有效下標為0 ~ size-1)與連結串列長度size相等時,則代表呼叫next()方法時,將不會返回任何有效元素(丟擲異常或者返回null)。
方法介紹
public boolean hasNext() {
return nextIndex < size;
}
判斷連結串列迭代器下次呼叫next方法時,是否還能返回有效的元素。由於連結串列中元素的有效下標範圍時0 ~ size-1,因此當迭代器遊標位置小於連結串列長度size時,則返回true,否則返回false。
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
返回列表迭代器當前遊標對應的元素。在進行元素訪問之前,需要進行併發修改的校驗。由程式碼可以看出,LinkedList迭代器的原生迭代器丟擲異常來代表連結串列中不再有有效元素,而不是返回一個特殊值(null)。
public boolean hasPrevious() {
return nextIndex > 0;
}
判斷列表迭代器下次呼叫previous
時,是否還能返回有效的元素。當列表迭代器的遊標大於0時,則代表列表迭代器可以向前遍歷,返回true,否則返回false。
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
返回迭代器當前遊標的前一個元素。注意,在呼叫本方法時,當迭代器位於連結串列的尾端時,需要特殊處理。因為迭代器位於連結串列尾端時,迭代器中的的next對應的節點為null。
public int nextIndex() {
return nextIndex;
}
返回連結串列下次呼叫next()
方法返回元素的下標,與nextIndex相等。
public int previousIndex() {
return nextIndex - 1;
}
返回連結串列下次呼叫previous()
方法返回元素的下標,一般的值為:nextIndex - 1。
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
刪除連結串列上次呼叫next()
或者previous()
訪問的元素。當迭代器上次訪問的元素為當前遊標指向的元素時(一般為呼叫previous()方法,這時連結串列最後一次訪問的元素與連結串列迭代器遊標指向的元素相同),則需要做指標的移動,否則做迭代器遊標移動。
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
用e替代連結串列迭代器最後一次返回的元素。
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
在連結串列迭代器當前指向的節點之前新增一個新節點,如果迭代器指向連結串列末尾時,則將新元素追加到連結串列的尾部。
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
這是Java 8為Iterator新增的預設方法,該方法可使用Lambda表示式來遍歷集合元素
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
檢測當前連結串列是否有併發結構修改,如果有,則丟擲併發修改異常。
反向迭代器(DescendingIterator)
屬性介紹
private final ListItr itr = new ListItr(size());
基連結串列迭代器實現,初始化時,將迭代器的遊標置於連結串列尾部,列表迭代器初始化遊標為size,而不是0。
方法介紹
public boolean hasNext() {
return itr.hasPrevious();
}
判斷反向迭代器是否仍有有效元素,基於連結串列迭代器的hasPrevious()
實現。
public E next() {
return itr.previous();
}
返回反向迭代器的下一個元素,基於連結串列迭代器的previous()
實現。
public void remove() {
itr.remove();
}
刪除反向迭代器上一次訪問的元素。
屬性介紹
transient int size = 0;
連結串列長度,即:連結串列中有效元素的個數;
transient Node<E> first;
連結串列中的第一個節點,需要滿足的條件為:(first == null && last == null)
(連結串列為空時)或者(first.prev == null && first.item != null)
(連結串列不為空時)。
transient Node<E> last;
連結串列中的最後一個節點,需要滿足的條件為:(first == null && last == null)
(連結串列為空時)或者(last.next == null && last.item != null)
(連結串列不為空時)。
構造方法
public LinkedList() {
}
連結串列預設構造方法,生成一個不包含任何元素的空連結串列。
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
生成一個包含集合c
中所有元素的連結串列。元素在連結串列中的順序與集合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++;
}
將e作為連結串列新的頭節點插入到連結串列中。連結串列中指標的移動與C語言中在連結串列頭部插入一個新元素基本相同,注意:C語言中的連結串列一般帶有一個頭節點(即:僅代表連結串列的開始,並不儲存有效資料,next指標指向連結串列真實的第一個元素),而在LinkedList中是不包含這種型別的節點的。
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作為連結串列新的尾節點插入到連結串列中。
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++;
}
在指定節點succ前新增一個節點e。
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;
}
將節點f
及以前的元素從連結串列中移除。在jdk中,認為f就是該連結串列的頭節點,並將本方法宣告為private,防止該方法的濫用。實際上,該方法可以將f
及以前的元素從連結串列中移除,但是由於在本方法中,僅僅將f節點對應的資料進行了置空操作,用於協助GC機制的執行,因此如果該方法被濫用,刪除的f不是連結串列的頭節點時,可能出現記憶體溢位的問題。
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;
}
將節點f
及以後的元素從連結串列中移除,與unlinkFirst
類似,宣告為private,防止濫用導致的記憶體溢位。
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;
}
將節點x
從連結串列中移除,並做指標的移動,其移動規則與C語言中類似。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
返回連結串列中的第一個有效元素,當連結串列為空時候,丟擲NoSuchElementException。
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
返回連結串列中的最後一個有效元素,當連結串列為空時候,丟擲NoSuchElementException。
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
刪除連結串列的第一個有效元素,當連結串列為空時候,丟擲NoSuchElementException。
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
刪除連結串列的最後一個有效元素,當連結串列為空時候,丟擲NoSuchElementException。
public void addFirst(E e) {
linkFirst(e);
}
在連結串列頭部插入一個新的節點。
public void addLast(E e) {
linkLast(e);
}
在連結串列尾部插入一個新的節點。
public boolean contains(Object o)
判斷連結串列中是否包含元素o
,如果包含,返回true,否則返回false。
public int size()
返回連結串列中有效元素的個數。
public boolean add(E e)
將元素e追加到連結串列的尾部,與addLast
方法功能基本相同,本方法在插入成功後會返回true。
public boolean remove(Object o)
從連結串列中刪除第一個與元素o
相等的節點,如果有該元素,則返回true,否則返回false。
public boolean addAll(Collection<? extends E> c)
將集合c中的所有元素追加到連結串列的末尾,其追加順序與c的迭代器返回元素的順序相同。但是,如果在此期間,c發生了結構性修改,則可能導致不可預期的結果。
public boolean addAll(int index, Collection<? extends E> c)
將集合c中所有的元素追加到連結串列的指定位置,其追加順序與c的迭代器返回元素的順序相同。其首先呼叫c的toArray方法,將c轉化為一個數組,而後遍歷這個陣列,將陣列中的元素依次插入到指定位置中。
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++;
}
刪除連結串列中所有的節點。注意,在刪除的過程中,需要從頭節點開始遍歷連結串列中的每一個節點,將每個節點的均設為null,保證GC機制可以回收連結串列佔用的記憶體。
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;
}
將連結串列指定位置的元素替換為element
,並將該位置原始值返回。
public void add(int index, E element)
在連結串列的指定位置插入一個新元素,其內部基於函式linkLast(element)
和linkBefore(element, node(index))
實現。
public E remove(int 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;
}
}
返回連結串列指定位置的節點。其使用右移操作判斷需要檢索的index位於連結串列的前端還是後端,而後決定採取正向遍歷還是逆向遍歷訪問節點。
public int indexOf(Object o)
返回元素o在連結串列中第一次出現的位置。當元素o在連結串列中不存在時,返回-1。
public int lastIndexOf(Object o)
返回元素o在連結串列中最後一次出現的位置。當元素o在連結串列中不存在時,返回-1。
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
返回連結串列中的第一個有效元素,當連結串列為空時,返回null。
public E element()
返回連結串列中的第一個有效元素,實現邏輯與getFirst()
相同,在連結串列為空時,丟擲NoSuchElementException異常。
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
返回並刪除連結串列中的第一個元素,當連結串列為空時,返回null。
public E remove()
返回並刪除連結串列中的第一個元素,實現邏輯與removeFirst()
相同,當連結串列為空時,丟擲NoSuchElementException異常。
public boolean offer(E e)
將元素e追加到連結串列的尾部,實現邏輯與add(e)
相同。
public boolean offerFirst(E e)
將元素e追加到連結串列的頭部,實現邏輯與addFirst(e)
相同。
public boolean offerLast(E e)
將元素e追加到連結串列的尾部,實現邏輯與addLast(e)
相同。
public E peekFirst()
返回連結串列的第一個有效元素,實現邏輯與peek()
相同。
public E peekLast()
返回連結串列的最後一個有效元素,當連結串列為空時,返回null。
public E pollFirst()
返回並刪除連結串列的第一個有效元素,實現邏輯與poll()
相同。
public E pollLast()
返回並刪除連結串列的最後一個有效元素,當連結串列為空時,返回null,而removeLast()
方法則會丟擲NoSuchElementException異常。
public void push(E e)
將元素e追加到連結串列的頭部,與函式addFirst()
等價,且內部基於addFirst()
實現。
public E pop()
返回並刪除連結串列的第一個元素,其內部基於removeFirst()
實現,因此當連結串列為空時,則會丟擲NoSuchElementException異常。
public boolean removeFirstOccurrence(Object o)
刪除元素o在列表中第一次出現的節點。
public boolean removeLastOccurrence(Object o)
刪除元素o在列表中最後一次出現的節點。
public ListIterator<E> listIterator(int index)
返回由連結串列指定位置開始的列表迭代器。
public Iterator<E> descendingIterator()
返回連結串列的一個反向基礎迭代器。
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;
}
將連結串列轉化為陣列。其基本思想為:申請一個與連結串列長度相同的陣列,而後正向遍歷連結串列,將連結串列節點中的元素設定到陣列的指定位置中,並將其返回。
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;
}