Java 中的 LinkedList

本篇文章是【Java集合系列】文章的第二篇敷搪,本系列將會逐個分析 Java 中的常用集合的特性及實現(xiàn)啡捶,然后對比不同場景下應該選擇哪種集合使用口蝠。

List 系列

LinkedList

實現(xiàn)了 List 以及 Deque雙向鏈表柿究,元素允許為 null诉字,所以 LinkedList 同時具備 List 以及 Deque 的特性多糠。
跟 ArrayList 一樣谨履,LinkedList 也是非線程安全的,可以使用包裝方法獲取同步對象:

List list = Collections.synchronizedList(new LinkedList(...));

iterator以及listIterator同樣也被設計為 fail-fast熬丧。

使用特性

LinkedList 內部實現(xiàn)上是個鏈表笋粟,所以可以把它當作鏈表使用。

LinkedList 同樣還可以代替 Stack 類當做來使用(官方也推薦這么做)析蝴。

但不合適作為 List 使用害捕,List 相關的方法會比較耗時,性能低闷畸。

鏈表和棧相關的操作基本都可以在固定時間內完成尝盼,但是 List 特性的操作,也就是通過索引訪問元素的方法get需要 O(n) 的時間佑菩,不推薦使用盾沫。

公開方法

因為雙端隊列也是基于隊列的,所以 LinkedList 中的方法大體可以分為三種:

  • 列表方法
  • 隊列方法
  • 雙端隊列方法

下面分別看下殿漠。

列表方法

列表方法比較簡單赴精,這里就不贅述了,可以看 ArrayList 那一節(jié)文章绞幌。

隊列

隊列是一種先進先出(FIFO)的數(shù)據(jù)結構蕾哟,Java 中的接口 Queue 描述了隊列這種數(shù)據(jù)結構。

方法名 描述 備注
add(e) 將元素插入隊列 未超出容量限制則添加成功返回 true,否則拋出 IllegalStateException
offer(e) 將元素插入隊列 未超出容量限制的情況下添加谭确,否則返回 false
remove() 返回并刪除隊列的頭 隊列為空:NoSuchElementException
poll() 返回并刪除隊列的頭 隊列為空返回 null
element() 返回隊列的頭 隊列為空:NoSuchElementException
peek() 返回隊列的頭 隊列為空返回 null

我們可以將上表按照功能分為三類:插入帘营、移除、檢索逐哈。
每種操作又提供了兩種方法:拋出異撤移或返回特殊值。

操作 拋出異常 返回特殊值
插入 add(e) offer(e)
移除 remove() poll()
檢索 element() peek()

上面就是隊列中提供的方法了昂秃,Java 大概是考慮到使用方便問題禀梳,所以給每種方法都提供了拋出異常或者返回特殊值的方式械蹋,也導致方法數(shù)量多了一倍,而對于雙端隊列來說羞芍,上述的每種方法又會多出兩個哗戈,看起來很多,但我們只要知道本質上就是上述的三種操作就行荷科,其他的都是變種而已唯咬。

雙端隊列

作為雙端隊列,比隊列多出的特性將通過給隊列中的每個操作方法提供兩個變種方法來實現(xiàn)畏浆,比如隊列中的 add 方法胆胰,雙端隊列中將提供addFirstaddLast方法,用于將元素添加到或者刻获。

由于雙端隊列的特性導致還可以很輕易的實現(xiàn)的功能蜀涨,可以用來替換 Stack 類來使用,所以 Deque 中又提供了兩個用于實現(xiàn)棧結構的方法蝎毡。

方法名 描述 備注
push(E e) 將元素壓入棧頂
pop() 彈出棧的頂部元素 列表為空拋出 NoSuchElementException

Deque 還提供了從后向前遍歷的descendingIterator的迭代器厚柳,元素將從最后一個開始遍歷到第一個元素。

總結

下面來總結一下沐兵。
LinkedList 作為隊列(FIFO)使用時的方法:

隊列方法 等效的雙端隊列方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

LinkedList 作為堆棧(FILO)使用時的方法:

堆棧方法 等效的雙端隊列方法
push(e) addFirst(e)
pop(e) removeFirst(e)
peek() peekFirst()

源碼

我們先看看鏈表的節(jié)點是如何定義的:

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

因為是雙向鏈表别垮,所以每個節(jié)點除了保存下一個節(jié)點的引用外,還保存了上一個節(jié)點的引用扎谎。
所以與 ArrayList 不同的是碳想,LinkedList 并不是使用數(shù)組存儲數(shù)據(jù),而是通過上面 Node 形成的鏈表來存儲毁靶。

我們先來通過一個簡單的add方法看看實現(xiàn):

public boolean add(E e) {
    linkLast(e);
    return true;
}
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++;
}

主要就調用了linkLast方法將元素添加到鏈表的末尾胧奔,這就是個鏈表的操作。
除了linkLast之外還有linkFirst用于將元素添加到鏈表頭部预吆。
諸如addFirst,push,offer之類的添加元素的操作最終都是通過上面兩個方法實現(xiàn)的葡盗。

再來看看移除操作poll:

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}
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;
}

使用unlinkFirst方法將元素從鏈表頂部移除,對應的還有unlinkLast方法。
同樣觅够,類似removeFirst,pop,pollFirst等操作也是通過上面兩個方法實現(xiàn)的胶背。

上面的兩種操作都是直接對鏈表的頭尾操作,都可以在固定時間復雜度內完成喘先,實現(xiàn)也比較簡單钳吟。
但考慮到 LinkedList 還實現(xiàn)了 List 接口,具備了 List 的特性窘拯,例如通過下標獲取到指定值红且,這個實現(xiàn)就比較復雜了,也會消耗掉 O(n) 的時間涤姊。

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
Node<E> node(int 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;
    }
}

get方法通過調用node方法獲取到指定索引的值暇番,由于鏈表的特性使然,無法直接通過索引獲取到元素思喊,需要從端點開始逐個向內遍歷壁酬,直到遍歷到指定索引再將元素返回。
node方法考慮到性能問題恨课,在遍歷前會先判斷index值靠前還是靠后舆乔,然后選擇是從頭還是尾開始遍歷,充分利用了雙向鏈表的特性剂公。

好了希俩,上面幾個方法就是 LinkedList 源碼中最具有代表性的幾個方法了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纲辽,一起剝皮案震驚了整個濱河市颜武,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拖吼,老刑警劉巖盒刚,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绿贞,居然都是意外死亡因块,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門籍铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涡上,“玉大人,你說我怎么就攤上這事拒名》岳ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵增显,是天一觀的道長雁佳。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么糖权? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任堵腹,我火速辦了婚禮,結果婚禮上星澳,老公的妹妹穿的比我還像新娘疚顷。我一直安慰自己,他們只是感情好禁偎,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布腿堤。 她就那樣靜靜地躺著,像睡著了一般如暖。 火紅的嫁衣襯著肌膚如雪笆檀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天盒至,我揣著相機與錄音酗洒,去河邊找鬼。 笑死妄迁,一個胖子當著我的面吹牛寝蹈,可吹牛的內容都是我干的李命。 我是一名探鬼主播登淘,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼封字!你這毒婦竟也來了黔州?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阔籽,失蹤者是張志新(化名)和其女友劉穎流妻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笆制,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡绅这,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了在辆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片证薇。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匆篓,靈堂內的尸體忽然破棺而出浑度,到底是詐尸還是另有隱情,我是刑警寧澤鸦概,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布箩张,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏先慷。R本人自食惡果不足惜饮笛,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望熟掂。 院中可真熱鬧缎浇,春花似錦、人聲如沸赴肚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽誉券。三九已至指厌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踊跟,已是汗流浹背踩验。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留商玫,地道東北人箕憾。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像拳昌,于是被迫代替她去往敵國和親袭异。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容