Linkedlist就是這么簡單

一.?概述

LinkedList 是 Java 集合中比較常用的數(shù)據(jù)結(jié)構(gòu)卢肃,與 ArrayList 一樣幻锁,實現(xiàn)了 List 接口敞映,只不過 ArrayList 是基于數(shù)組實現(xiàn)的沸伏,而 LinkedList 是基于鏈表實現(xiàn)的糕珊。所以 LinkedList 插入和刪除方面要優(yōu)于 ArrayList,而隨機(jī)訪問上則 ArrayList 性能更好毅糟。

除了 LIst 接口之外红选,LinkedList 還實現(xiàn)了 Deque,Cloneable姆另,Serializable 三個接口喇肋。這說明該數(shù)據(jù)結(jié)構(gòu)支持隊列,克隆和序列化操作的蜕青。與 ArrayList 一樣苟蹈,允許 null 元素的存在,且是不支持多線程的右核。

二.?源碼解讀

屬性

LinkedList 提供了以下三個成員變量慧脱。size,first贺喝,last菱鸥。

transient int size = 0;

transient Node<E> first;

transient Node<E> last;

其中 size 為 LinkedList 的大小,first 和 last 分別為鏈表的頭結(jié)點和尾節(jié)點躏鱼。Node 為節(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;

? ?}

}

Node 是 LInkedList 的內(nèi)部類,定義了存儲的數(shù)據(jù)元素染苛,前一個節(jié)點和后一個節(jié)點鹊漠,典型的雙鏈表結(jié)構(gòu)主到。

構(gòu)造方法

public LinkedList() {}

public LinkedList(Collection<? extends E> c) {

? ?this();

? ?addAll(c);

}

LinkedList 提供了兩個構(gòu)造方法:LinkedList() 和 LinkedList(Collection<? extends E> c)。

LinkedList() 僅僅構(gòu)造一個空的列表躯概,沒有任何元素登钥。size = 0。first 和 last 都為 null娶靡。

后一個構(gòu)造方法構(gòu)造一個包含指定 Collection 中所有元素的列表牧牢,該構(gòu)造方法首先會調(diào)用空的構(gòu)造方法,然后通過 addAll() 的方式把 Collection 中的所有元素添加進(jìn)去姿锭。

調(diào)用 addAll() 方法塔鳍,傳入當(dāng)前的節(jié)點個數(shù) size,此時 size 為

檢查 index 是否越界

將 collection 轉(zhuǎn)換成數(shù)組

遍歷數(shù)組呻此,將數(shù)組里面的元素創(chuàng)建為節(jié)點轮纫,并按照順序連起來。

修改當(dāng)前的節(jié)點個數(shù) size 的值

操作次數(shù) modCount 自增 1.

public boolean addAll(Collection<? extends E> c) {

? ?return addAll(size, c);

}

public boolean addAll(int index, Collection<? extends E> c) {

? ?checkPositionIndex(index);

? ?Object[] a = c.toArray();

? ?int numNew = a.length;

? ?if (numNew == 0)

? ? ? ?return false;

? ?Node<E> pred, succ;

? ?if (index == size) {

? ? ? ?succ = null;

? ? ? ?pred = last;

? ?} else {

? ? ? ?succ = node(index);

? ? ? ?pred = succ.prev;

? ?}

? ?for (Object o : a) {

? ? ? ?@SuppressWarnings("unchecked") E e = (E) o;

? ? ? ?Node<E> newNode = new Node<>(pred, e, null);

? ? ? ?if (pred == null)

? ? ? ? ? ?first = newNode;

? ? ? ?else

? ? ? ? ? ?pred.next = newNode;

? ? ? ?pred = newNode;

? ?}

? ?if (succ == null) {

? ? ? ?last = pred;

? ?} else {

? ? ? ?pred.next = succ;

? ? ? ?succ.prev = pred;

? ?}

? ?size += numNew;

? ?modCount++;

? ?return true;

}

add 操作

添加元素到鏈表末尾

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

}

add 方法直接調(diào)用了 linkLast 方法趾诗,而 linkLast 方法是不對外開放的蜡感。該方法做了三件事情蹬蚁,新增一個節(jié)點恃泪,改變其前后引用,將 size 和 modCount 自增 1犀斋。其中 modCount 是記錄對集合操作的次數(shù)贝乎。

在指定的位置插入元素

public void add(int index, E element) {

? ?checkPositionIndex(index);

? ?if (index == size)

? ? ? ?linkLast(element);

? ?else

? ? ? ?linkBefore(element, node(index));

}

private void checkPositionIndex(int index) {

? ?if (!isPositionIndex(index))

? ? ? ?throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

private boolean isPositionIndex(int index) {

? ?return index >= 0 && index <= size;

}

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

}

首先檢查下標(biāo)是否越界,然后判斷如果 index == size 則添加到末尾叽粹,否則將該元素插入的 index 的位置览效。其中 node(index) 是獲取 index 位置的節(jié)點,linkBefore 負(fù)責(zé)把元素 e 插入到 succ 之前虫几。

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;

? ?}

}

可以看出 node() 方法這里寫的還是挺贊的锤灿,不是傻乎乎的從頭到尾或者從尾到頭遍歷鏈表,而是將 index 與 當(dāng)前鏈表的一半做對比辆脸,比一半小從頭遍歷但校,比一半大從后遍歷。對于數(shù)據(jù)量很大時能省下不少時間啡氢。

get 操作

很簡單状囱,首先獲取節(jié)點,然后返回節(jié)點的數(shù)據(jù)即可倘是。

public E get(int index) {

? ?checkElementIndex(index);

? ?return node(index).item;

}

remove 操作

移除指定位置的元素

public E remove(int index) {

? ?checkElementIndex(index);

? ?return unlink(node(index));

}

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; // 如果移除的是頭節(jié)點亭枷,那么頭結(jié)點后移

? ?} else {

? ? ? ?prev.next = next;

? ? ? ?x.prev = null; ?// 釋放節(jié)點的前一個元素

? ?}

? ?if (next == null) {

? ? ? ?last = prev; // 如果移除的是尾節(jié)點,尾結(jié)點前移

? ?} else {

? ? ? ?next.prev = prev;

? ? ? ?x.next = null; ?// 釋放節(jié)點的后一個元素

? ?}

? ?x.item = null; // 釋放節(jié)點數(shù)據(jù)

? ?size--;

? ?modCount++;

? ?return element;

}

先檢查下標(biāo)是否越界搀崭,然后調(diào)用 unlink 釋放節(jié)點叨粘。

移除指定元素

public boolean remove(Object o) {

? ?if (o == null) {

? ? ? ?for (Node<E> x = first; x != null; x = x.next) {

? ? ? ? ? ?if (x.item == null) {

? ? ? ? ? ? ? ?unlink(x);

? ? ? ? ? ? ? ?return true;

? ? ? ? ? ?}

? ? ? ?}

? ?} else {

? ? ? ?for (Node<E> x = first; x != null; x = x.next) {

? ? ? ? ? ?if (o.equals(x.item)) {

? ? ? ? ? ? ? ?unlink(x);

? ? ? ? ? ? ? ?return true;

? ? ? ? ? ?}

? ? ? ?}

? ?}

? ?return false;

}

判斷要移除的元素是否為 null,然后在遍歷鏈表,找到鈣元素第一次出現(xiàn)的位置升敲,移除并返回 true袍镀。

像其他的常用方法如:getFirst, getLast, removeFirst, removeLast, addFirst, addLast 等都很簡單,掃一眼源碼就能懂冻晤,我這里就不寫了苇羡。

迭代器

LInkedList 的 iterator() 方法是在其父類 AbstractSequentialList 中定義的,最終一路 debug 到 LinkedList 類這里鼻弧。其中 index 為 零设江。

public ListIterator<E> listIterator(int index) {

? ?checkPositionIndex(index);

? ?return new ListItr(index);

}

我們來看看 ListItr。

private Node<E> lastReturned;

? ?private Node<E> next;

? ?private int nextIndex;

? ?private int expectedModCount = modCount;

? ?ListItr(int index) {

? ? ? ?// assert isPositionIndex(index);

? ? ? ?next = (index == size) ? null : node(index);

? ? ? ?nextIndex = index;

? ?}

? ?public boolean hasNext() {

? ? ? ?return nextIndex < size;

? ?}

? ?public E next() {

? ? ? ?checkForComodification();

? ? ? ?if (!hasNext())

? ? ? ? ? ?throw new NoSuchElementException();

? ? ? ?lastReturned = next;

? ? ? ?next = next.next;

? ? ? ?nextIndex++;

? ? ? ?return lastReturned.item;

? ?}

? ? final void checkForComodification() {

? ? ? ?if (modCount != expectedModCount)

? ? ? ? ? ?throw new ConcurrentModificationException();

? ?}

}

篇幅有限 攘轩,我就只貼主要代碼了叉存。由源碼可以看出初始化 ListItr 時,將 nextIndex 指向 index度帮, 也就是零歼捏。如果該集合為空,那么 index == size 為 true笨篷,next 指向?null瞳秽,否則 next 指向下標(biāo)為零的元素,也就是第一個率翅。

hasNext 直接返回 nextIndex < size 簡單明了练俐。下面看看 next 方法,首先檢查 expectedModCount 與 modCount 是否相等冕臭,看似無關(guān)緊要的代碼保證了集合在迭代過程中不被修改[包括新增刪除節(jié)點等]腺晾。然后將 lastReturned 指向 next,next 后移一個節(jié)點辜贵,nextIndex 自增 1悯蝉,并返回 lastReturned 節(jié)點的元素。

總結(jié)

1托慨、從源碼可以看出 LinkedList 是基于鏈表實現(xiàn)的鼻由。如下圖:

2、在查找和刪除某元素時榴芳,區(qū)分該元素為 null和不為 null 兩種情況來處理嗡靡,LinkedList 中允許元素為 null。

3窟感、基于鏈表實現(xiàn)不存在擴(kuò)容問題讨彼。

4、查找時先判斷該節(jié)點位于前半部分還是后半部分柿祈,加快了速度

5哈误、因為基于鏈表哩至,所以插入刪除極快,查找比較慢蜜自。

6菩貌、實現(xiàn)了棧和隊列的相關(guān)方法,所以可作為棧重荠,隊列箭阶,雙端隊列來用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戈鲁,一起剝皮案震驚了整個濱河市仇参,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婆殿,老刑警劉巖诈乒,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異婆芦,居然都是意外死亡怕磨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門消约,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肠鲫,“玉大人,你說我怎么就攤上這事荆陆√步欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵被啼,是天一觀的道長。 經(jīng)常有香客問我棠枉,道長浓体,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任辈讶,我火速辦了婚禮命浴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贱除。我一直安慰自己生闲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布月幌。 她就那樣靜靜地躺著碍讯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扯躺。 梳的紋絲不亂的頭發(fā)上捉兴,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天蝎困,我揣著相機(jī)與錄音,去河邊找鬼倍啥。 笑死禾乘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虽缕。 我是一名探鬼主播始藕,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氮趋!你這毒婦竟也來了鳄虱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凭峡,失蹤者是張志新(化名)和其女友劉穎拙已,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摧冀,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡倍踪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了索昂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片建车。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖椒惨,靈堂內(nèi)的尸體忽然破棺而出缤至,到底是詐尸還是另有隱情,我是刑警寧澤康谆,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布领斥,位于F島的核電站,受9級特大地震影響沃暗,放射性物質(zhì)發(fā)生泄漏月洛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一孽锥、第九天 我趴在偏房一處隱蔽的房頂上張望嚼黔。 院中可真熱鬧,春花似錦惜辑、人聲如沸唬涧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碎节。三九已至,卻和暖如春撵彻,著一層夾襖步出監(jiān)牢的瞬間钓株,已是汗流浹背实牡。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留轴合,地道東北人创坞。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像受葛,于是被迫代替她去往敵國和親题涨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容