LinkedList 的 API 與資料結構

2022-03-30 20:13:47 字數 4569 閱讀 2215

linkedlist 是 list 介面和 deque 介面的雙向鍊錶實現,它所有的 api 呼叫都是基於對雙向鍊錶的操作。本文將介紹 linkedlist 的資料結構和分析 api 中的演算法。

linkedlist 的資料結構是乙個雙向鍊錶,它有兩個成員變數:first 和 last,分別指向雙向佇列的頭和尾。

nodefirst;

nodelast;

這裡「雙向」的含義是相對單鏈表而言的,雙向鍊錶的節點不僅有後繼,還有前驅。linkedlist 中雙向鍊錶的節點是乙個個的 node,它是 linkedlist 的乙個靜態內部類。其定義如下。node 是乙個泛型類,泛型引數是存放在 linkedlist 中的值的型別。

private static class node

}

乙個包含若干元素的 linkedlist 如下圖所示。

linkedlist 的 api 都是基於雙端佇列的操作來實現的,這些操作被封裝成了一系列的 private 方法。下面對這些私有方法進行分析。

linkedlist 通過 linkfirst(e) 與 linklast(e) 分別往雙向鍊錶的頭部和尾部插入元素。插入元素時需要考慮兩種情況:1)雙向鍊錶中不包含元素;2)雙向鍊錶中已經包含了元素。插入元素屬於修改操作,因此運算元 modcount 需要進行自增。更多關於 modcount 的說明可以參考這篇:(arraylist 原始碼分析)[ 原始碼如下,linklast(e) 原始碼與前者類似。

private void linkfirst(e e)
linkbefore(e, succ) 在元素 succ 前面插入元素 e,它需要考慮兩種情況:1)succ 的前驅為空;2)succ 的前驅不為空。在指定元素前面插入操作時間複雜度為 o(1),相對 arraylist 時間複雜度為 o(n) 插入來說,效率極高。(n 為 list 中已有元素的個數)

void linkbefore(e e, nodesucc)
unlinkfirst(f) 操作將移除頭部節點,它需要考慮兩種情況:1)鍊錶中只有 1 個元素;2)鍊錶中有超過 1 個元素。

private e unlinkfirst(nodef)
unlink(x) 在移除指定元素時也是小心翼翼。這個方法在功能上可以替代 unlinkfirst(f) 和 unlinklast(f),不過因為 linkedlist 對頭和尾的操作及其頻繁,因此用單獨的更高效的函式進行處理可以在一定程度上提公升效能。

e unlink(nodex)  else 

// 處理後繼指標

if (next == null) else

x.item = null; // 幫助 gc

size--;

modcount++;

return element;

}

因為是鍊錶結構,要根據位置獲取節點只能以迭代的方式進行,時間複雜度為 o(n),這裡的 node(index) 方法做了一點優化:若索引號 index 在前半部分,則從頭節點開始遍歷;若索引好 index 在後半部分,則從尾節點開始遍歷。

nodenode(int index)  else 

}

以上部分就是 linkedlist 中雙端佇列的操作了,不過這些方法都被 private 修飾,因此開發人員無法直接呼叫它們,不過 linkedlist 所暴露出來的 api 幾乎都是呼叫這些 private 方法來完成操作的。下面介紹 linkedlist 的相關 api。

linkedlist 的構造方法有兩個,乙個是無參構造方法,另乙個構造方法可以傳入乙個集合。

linkedlist 內部維護了乙個成員變數size,每次插入或者刪除元素時都會更新該變數的值。size() 方法僅僅是返回了該變數的值。

通過 size 的值來判斷,size 為 0 即表示 linkedlist 為空。

indexof(o) 將返回指定元素 o 在 linkedlist 中首次出現的位置(頭節點到尾節點方向),它需要從頭節點開始遍歷雙向鍊錶。如果元素不存在,則返回 -1。傳入的 o 可以為 null,原始碼中專門分了兩個分支來處理傳入的 o 為 null 和非 null 的問題。時間複雜度為 o(n),其中 n 為 linkedlist 中元素的數量。

public int indexof(object o) 

} else

}return -1;

}

lastindexof(o) 與 indexof(o) 相反,它從雙向鍊錶的尾部開始遍歷,返回元素 o 在 linkedlist 中最後出現的位置。返回 -1 表示不包含元素 o。

contains(o) 方法呼叫了 indexof(o),通過檢查返回值是否為 -1 來判斷 linkedlist 中是否包含了 o。

add(e) 將元素 e 新增到雙向鍊錶的末尾,此方法直接呼叫了 linklast(e) 方法完成了操作。

public boolean add(e e)
此方法將元素新增到指定的索引位置,它分了兩種情況:一種是 index == size,直接新增到雙向鍊錶尾部即可;另一種是非新增到尾部,需要先迭代找到索引位置為 index 的元素,然後將新元素插入到它前面。時間複雜度為 o(n)。

public void add(int index, e element)
此方法呼叫了 node(index) 獲取 element 所在的 node,然後將 element 掛到了 node 上。時間複雜度為 o(n)。

public e set(int index, e element)
remove(index) 移除索引為 index 的元素,先根據索引獲取節點,然後呼叫 unlink(e) 移除節點。

public e remove(int index)
remove(o) 將查詢元素 o 在 linkedlist 中第一次所在的節點,然後移除該節點。這一操作需要遍歷雙向鍊錶。需要注意的是 remove(o) 並不會移除所有的 o ,只會移除第 1 個。

public boolean remove(object o) 

}} else }}

return false;

}

linkedlist 沒有單獨的內部類實現 iterator 介面,呼叫 iterator() 方法返回的本質是乙個 listitr,和 listiterator() 返回的是一樣的迭代器。

listitr 允許從指定下標位置開始迭代,下標位置通過構造方法的引數傳入。

listitr(int index)
linkedlist 有兩個獲取 listitr 的 api,分別是 listiterator() 與 listiterator(index),二者本質一樣。

public listiteratorlistiterator() 

public listiteratorlistiterator(int index)

listitr 在迭代的過程中 linkedlist 不能夠被其它的執行緒改變,否則可能丟擲 concurrentmodificationexception。這是一種 fail-fast 策略,通過修改數 modcount 來實現,前面可以看到,凡是會改變鍊錶結構的操作都會更新 modcount 的值。在迭代的過程中不斷檢查 modcount 是否和期望的值一致,如果不一致,則說明有其它的執行緒修改了雙向鍊錶的結構。此時 linkedlist 中的資料可能出現錯誤,但如果沒有 fail-fast 機制,這種錯誤可能不會立即暴露出來,系統可能需要執行很長時間才暴露,到那時可能已經產生嚴重後果了,後面再來排查錯誤原因也及其困難。

通過 modcount 機制來探測這類難以錯誤,一旦探測到,立即報告,這就是 fail-fast 機制。不過由於多執行緒操作本身存在著不確定性,modcount 也並非一定能夠探測到這種錯誤。為了避免這種錯誤,在多執行緒訪問同乙個 linkedlist 物件時應該進行執行緒同步,最好就時不讓多執行緒訪問同乙個 linkedlist。

不過 listitr 允許迭代器自身修改 linkedlist,它在修改之後會更新 modcount,支援的修改操作包括:

public void addfirst(e e)
public e getfirst()
public e removefirst()
linkedlist 內部是雙向鍊錶結構,新增,刪除元素很方便,支援存放 null 元素。

linkedlist 實現了 list 和 deque 介面。作為 list,linkedlist 適用於數量未知且需要大量增刪操作情形,若需要隨機訪問或者大量查詢,應該使用 arraylist;作為 deque,linkedlist 適用於容量未知的情形,如果容量已知,則使用 arraydeque 效率會更高一些。

linkedlist 是非執行緒安全的,多個執行緒同時訪問乙個 linkedlist 可能破壞其內部結構。

LinkedList與ArrayList的區別

我們依然從資料結構的角度看度這個問題。從命名上可以大致猜出來linkedlist的資料結構為鍊錶,arraylist的資料結構為陣列。能夠看到這裡它們的區別就一目了然了 它們的區別大致就和陣列和鍊錶的區別是一樣的。在在查詢和刪除操作中陣列的速度要優於鍊錶,這是因為陣列是按照下標來執行這兩個操作的,而...

ArrayList與LinkedList的區別

arraylist與linkedlist的區別 arraylist和linkedlist的大致區別如下 1.arraylist是實現了基於動態陣列的資料結構,linkedlist基於鍊錶的資料結構。2.對於隨機訪問get和set,arraylist覺得優於linkedlist,因為linkedlis...

ArrayList與LinkedList的區別

關於兩者的區別,先通過兩者對資料的操作進行對比 public static void main string args end system.currenttimemillis system.out.println arraylist第一次插入資料前後時間差 end start linkedlist...