LinkedList源碼分析

LinkedList

LinkedList是List接口的一個(gè)實(shí)現(xiàn)類崖蜜,內(nèi)部是基于一個(gè)雙向鏈表實(shí)現(xiàn)的焰雕。支持添加衷笋,移除,替換三種操作矩屁。
同時(shí)辟宗,LinkedList中的元素可以是任意類型的,包括null吝秕。當(dāng)需要隊(duì)列一樣的數(shù)據(jù)操作的時(shí)候使用LinkedList是很有用的泊脐。

先來(lái)看看LinkedList的繼承結(jié)構(gòu):

AbstractCollection

AbstractList

AbstractSequentialList

LinkedList

此外LinkedList還直接實(shí)現(xiàn)了一下的接口:

List<E>, Deque<E>, Queue<E>, Cloneable, Serializable

List接口在之前額ArrayList分析中已近講過(guò)了。這里就不贅述了烁峭。下面主要看看Deque<E>, Queue<E>這兩個(gè)接口容客。

Queue

Queue被設(shè)計(jì)用來(lái)做預(yù)先處理的收集器。它除了有繼承子Collection的功能實(shí)現(xiàn)外则剃,還有插入耘柱,刪除如捅,查找三個(gè)操作棍现。
并且這三個(gè)操作都有對(duì)應(yīng)的兩種類型的實(shí)現(xiàn)方法。一種會(huì)拋出異常镜遣,另一種則是返回一個(gè)指定的值己肮。在插入操作還會(huì)有容量限制,沒(méi)有容量限制的話能保證插入不會(huì)失敗悲关。

方法 說(shuō)明
boolean add(E e) 添加一個(gè)元素到隊(duì)列的末尾谎僻,如果隊(duì)列使用容量限制的話,插入成功寓辱,返回true艘绍,有容量限制且容量已滿則否則拋出異常
boolean offer(E e) 無(wú)容量限制則直接插入隊(duì)列末尾,返回true秫筏,否則調(diào)用add()進(jìn)行插入诱鞠,有異常返回false
E remove() 從隊(duì)列的首部移除一個(gè)元素,并返回該元素这敬。隊(duì)列已空則拋出異常航夺。
E poll() 從隊(duì)列的首部移除一個(gè)元素,并返回該元素崔涂。隊(duì)列已空則返回null阳掐。
E element() 返回隊(duì)列的末尾的元素,不執(zhí)行刪除操作。隊(duì)列已空則拋出異常缭保。
E peek() 返回隊(duì)列的末尾的元素汛闸,不執(zhí)行刪除操作。隊(duì)列已空則返回null涮俄。

Deque(其實(shí)是雙向隊(duì)列的縮寫(xiě))

繼承自Queue蛉拙。

Deque是一個(gè)線性收集器,支持從開(kāi)頭或者結(jié)尾添加或者刪除元素彻亲。Deque的實(shí)現(xiàn)類基本都不加入容量限制孕锄,但是保留有這個(gè)功能。Deque支持的操作有添加苞尝,移除畸肆,檢索元素。每種操作都有兩種類型宙址,一種是操作失敗直接拋出異常轴脐,另一種是返回一個(gè)指定的值。

方法 說(shuō)明
void addFirst(E e) 隊(duì)列沒(méi)有容量限制的話抡砂,將元素加入到隊(duì)列首部大咱。否則拋出異常
void addLast(E e) 隊(duì)列沒(méi)有容量限制的話,將元素加入到隊(duì)列末尾注益。否則拋出異常
boolean offerFirst(E e) 隊(duì)列沒(méi)有容量限制的話碴巾,將元素加入到隊(duì)列首部,返回true。否則則去調(diào)用addFirst()丑搔,如拋出異常則返回false
boolean offferLast(E e) 隊(duì)列沒(méi)有容量限制的話厦瓢,將元素加入到隊(duì)列末尾,返回true。否則則去調(diào)用addLast()啤月,如拋出異常則返回false
E removeFirst() 移除隊(duì)列第一個(gè)元素煮仇,如果隊(duì)列為空則拋出異常
E removeLast() 移除隊(duì)列最后一個(gè)元素,如果隊(duì)列為空則拋出異常
E pollFirst() 移除隊(duì)列第一個(gè)元素谎仲,如果隊(duì)列為空則返回null
E pollLast() 移除隊(duì)列最后一個(gè)元素浙垫,如果隊(duì)列為經(jīng)為空則返回null
E getFirst() 獲取但是不刪除第一個(gè)元素,隊(duì)列為空則拋出異常
E getLast() 獲取但是不刪除最后一個(gè)元素郑诺,隊(duì)列為空則拋出異常
E peekFirst() 獲取但是不刪除第一個(gè)元素夹姥,隊(duì)列為空則返回null
E peekLast() 獲取但是不刪除最后一個(gè)元素,隊(duì)列為空則返回null

在Deque和Queue中可能拋出的異常有:
IllegalStateException间景,ClassCastException佃声,NullPointerException,IllegalArgumentException

LinkedList

先來(lái)看看它的一個(gè)內(nèi)部靜態(tài)類倘要。
實(shí)際上是對(duì)數(shù)據(jù)的封裝圾亏,以及加上它的前后元素的引用十拣。其實(shí)和C語(yǔ)言中的鏈表的指針的意思是一樣的。

private static final class Link<ET> {
    ET data; //封裝的數(shù)據(jù)元素

    Link<ET> previous, next;//該元素指向它的前后元素的引用志鹃。

    Link(ET o, Link<ET> p, Link<ET> n) {
        data = o;//我們使用LinkedList的時(shí)候裝載的數(shù)據(jù)對(duì)象本身夭问。
        previous = p;
        next = n;
    }
}

內(nèi)部還有兩個(gè)迭代器內(nèi)部類:

  • LinkIterator
  • ReverseLinkIterator

我們看看LinkIterator的內(nèi)部實(shí)現(xiàn):

Link<ET> link, lastLink;//link當(dāng)前元素,lastLink最后一次訪問(wèn)到元素

//從指定位置開(kāi)始迭代
LinkIterator(LinkedList<ET> object, int location) {
    list = object;//隊(duì)列本身
    //得到原list中的操作記數(shù)曹铃。后續(xù)對(duì)list的操作都會(huì)使用到缰趋,判別是否存在并發(fā)操作
    if (location >= 0 && location <= list.size) {
    expectedModCount = list.modCount;
        link = list.voidLink;
        //查找的優(yōu)化,根據(jù)索引位置與list的size比較陕见,從頭向尾秘血,還是從末尾向開(kāi)頭開(kāi)始尋找指定位置的元素
        if (location < list.size / 2) {
            for (pos = -1; pos + 1 < location; pos++) {
                link = link.next;
            }
        } else {
            for (pos = list.size; pos >= location; pos--) {
                link = link.previous;
            }
        }
    } else {
        throw new IndexOutOfBoundsException();
    }
}

設(shè)置元素


public void set(ET object) {
    ...
      //lastLink就是最后一個(gè)訪問(wèn)到的元素  
        lastLink.data = object;
    ...  
}

添加元素

public void add(ET object) {
    if (expectedModCount == list.modCount) {
        Link<ET> next = link.next;//當(dāng)前元素的下一個(gè)元素
        Link<ET> newLink = new Link<ET>(object, link, next);//將新添加的數(shù)據(jù)封裝
        link.next = newLink;//link.next指向新元素
        next.previous = newLink;//當(dāng)前元素的下一個(gè)元素的previous指向新元素
        link = newLink;//當(dāng)前的節(jié)點(diǎn)更新為新加入的節(jié)點(diǎn)
        lastLink = null;//最后訪問(wèn)的節(jié)點(diǎn)置為空
        pos++;//這實(shí)際是新添加元素的節(jié)點(diǎn)位置索引值
        //同時(shí)自加LinkedList中的操作次數(shù)和迭代器中的操作次數(shù),不然肯定會(huì)報(bào)異常评甜,因?yàn)槊看?        //對(duì)list的操作都會(huì)檢查這兩個(gè)值是否相等灰粮。
        expectedModCount++;
        list.modCount++;
        list.size++;
    } else {
        throw new ConcurrentModificationException();
    }
public boolean hasNext() {
    return link.next != list.voidLink;//判斷一個(gè)節(jié)點(diǎn)的next域是否指向null,null就說(shuō)明到末尾了
}
//link為空值說(shuō)明沒(méi)有前繼幾點(diǎn)啦忍坷,用link.previous != null,可能link==null時(shí)粘舟,link.previous會(huì)報(bào)空指針異常。
public boolean hasPrevious() {
    return link != list.voidLink;
}

public ET next() {
    if (expectedModCount == list.modCount) {
        LinkedList.Link<ET> next = link.next;//當(dāng)前元素的下一個(gè)元素
        if (next != list.voidLink) {
            lastLink = link = next;//同時(shí)將保存當(dāng)前節(jié)點(diǎn)和最后訪問(wèn)的節(jié)點(diǎn)更新為剛得到的下一個(gè)元素
            pos++;
            return link.data;
        }
        throw new NoSuchElementException();//已到達(dá)末尾是調(diào)用next佩研,會(huì)拋異常
    }
    throw new ConcurrentModificationException();
}

public ET previous() {
      ...
        lastLink = link;
        link = link.previous;//直接從保存當(dāng)前節(jié)點(diǎn)信息的link的previous域獲得前繼節(jié)點(diǎn)
        pos--;
        return lastLink.data;
      ....
}
//其實(shí)就是add的逆操作柑肴,需要注意的是remove操作是將最后訪問(wèn)到的節(jié)點(diǎn)刪除的,也就是lastLink
public void remove() {
    ...
        Link<ET> next = lastLink.next;
        Link<ET> previous = lastLink.previous;
        next.previous = previous;
        previous.next = next;
        //當(dāng)前節(jié)點(diǎn)與最后訪問(wèn)的節(jié)點(diǎn)指向同意元素旬薯,next()或者previous()后晰骑,該條件為true
        if (lastLink == link) {
            pos--;
        }
        link = previous;//更新當(dāng)前節(jié)點(diǎn)為刪除節(jié)點(diǎn)的前一個(gè)元素
        lastLink = null;
        expectedModCount++;
        list.size--;
        list.modCount++;
      ...
}

add remove next previous set 這五個(gè)操作都會(huì)有以下異常判斷(并發(fā)操作異常)

if (expectedModCount == list.modCount) {
    ...
} else {
    throw new ConcurrentModificationException();
}

ReverseLinkIterator只是LinkIterator的反序迭代而已,理解好LinkIterator就很好理解ReverseLinkIterator了

迭代器都說(shuō)了這么多袍暴,下面我們看看LinkedList的主體部分:

//默認(rèn)構(gòu)造方法會(huì)創(chuàng)建一個(gè)空節(jié)點(diǎn)
public LinkedList() {
    voidLink = new Link<E>(null, null, null);//此時(shí)voidLink.previous==voidLink.next==null
    voidLink.previous = voidLink;//指向?qū)ο蟊旧硇┦蹋WC使用voidLink.previous不會(huì)空指針異常
    voidLink.next = voidLink;
}
public LinkedList(Collection<? extends E> collection) {
    this();//new一個(gè)LinkedList的時(shí)候隶症,總會(huì)調(diào)用到默認(rèn)構(gòu)造方法
    addAll(collection);
}

voidLink是維持整個(gè)list的關(guān)鍵所在政模,基本每個(gè)操作都離不開(kāi)它 看后面的分析就知道了。

添加元素的時(shí)候邏輯基本和上面迭代器中的邏輯一樣蚂会,只是add(E e),offerFirst(E e)內(nèi)部是直接調(diào)用addLastImpl(E e)執(zhí)行添加操作而已淋样,而指定位置的添加注意一點(diǎn)上面提及到根據(jù)插入點(diǎn)的索引與list的size的中點(diǎn)比較,從而選擇從頭開(kāi)始還是從尾開(kāi)始尋找該位置而已胁住。其實(shí)涉及查找的操作都會(huì)使用這個(gè)策略的趁猴。

//public void add(int location, E object)的片段而已
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;
    }
}
----------------------------------------------
public boolean add(E object) {
    return addLastImpl(object);
}

private boolean addLastImpl(E object) {
    Link<E> oldLast = voidLink.previous;
    Link<E> newLink = new Link<E>(object, oldLast, voidLink);
    voidLink.previous = newLink;
    oldLast.next = newLink;
    size++;
    modCount++;
    return true;
}

在addAll方法中國(guó)蜗字,插入邏輯是不變的肋坚,只是參數(shù)檢查而已

public boolean addAll(Collection<? extends E> collection) {
    int adding = collection.size();
    if (adding == 0) {
        return false;//collection沒(méi)有元素直接返回
    }
    //判斷是不是LinkedList的自己的相同對(duì)象缚去。是的話會(huì)拷貝它所有的元素到ArrayList中齐苛。
    Collection<? extends E> elements = (collection == this) ?
            new ArrayList<E>(collection) : collection;

    //由LinkedList的構(gòu)造方法可知鉴嗤,voidLink == voidLink.previous(初次調(diào)用的時(shí)候)
    //不是第一次調(diào)用時(shí)毁菱,voidLink.previous已經(jīng)指向了最后添加的那個(gè)元素了舍扰。詳見(jiàn)一下幾行代碼
    Link<E> previous = voidLink.previous;
    //循環(huán)插入collection中的元素
    for (E e : elements) {
        Link<E> newLink = new Link<E>(e, previous, null);
        previous.next = newLink;
        previous = newLink;
    }
    previous.next = voidLink;
    voidLink.previous = previous;
    //至此砖茸,會(huì)使得voidLink.next和voidLink.previous都指向最后一個(gè)添加的元素,
    size += adding;
    modCount++;
    return true;
}

我們?cè)賮?lái)看看addFirst的實(shí)現(xiàn):

public void addFirst(E object) {
    addFirstImpl(object);
}

private boolean addFirstImpl(E object) {
    //第一次addFirstImpl的時(shí)候voidLink.next==voidLink碉碉,否則則是上次添加在頭部的節(jié)點(diǎn)
    Link<E> oldFirst = voidLink.next;
    //此時(shí)newLink.previous = voidLink柴钻,頭結(jié)點(diǎn)的前繼肯定指向空節(jié)點(diǎn)嘛(不同于c語(yǔ)言指向的是NULL)
    //newLink.next == voidLink.next
    Link<E> newLink = new Link<E>(object, voidLink, oldFirst);
    voidLink.next = newLink;//看到?jīng)],voidLink.next保存著每次點(diǎn)在在頭部的節(jié)點(diǎn)信息
    oldFirst.previous = newLink;//oldFirst則是上一次添加在頭部的節(jié)點(diǎn)啦垢粮,它的previous指向newLink就順理成章啦
    size++;
    modCount++;
    return true;


}

到這里就能搞明白voidLink的previous和next的作用啦贴届,那么其他的操作都是基于這兩個(gè)東西操作的。
以下舉一些例子:

@Override
public boolean contains(Object object) {
    Link<E> link = voidLink.next;//頭節(jié)點(diǎn)
    //注意分兩種情況查找即可
    if (object != null) {
        while (link != voidLink) {
            if (object.equals(link.data)) {
                return true;
            }
            link = link.next;
        }
    } else {
        while (link != voidLink) {
            if (link.data == null) {
                return true;
            }
            link = link.next;
        }
    }
    return false;
}


@Override
public E get(int location) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;//這里蜡吧,下一步就用到next 和 previous了
        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;
            }
        }
        return link.data;
    }
    throw new IndexOutOfBoundsException();
}

@Override
public int indexOf(Object object) {
    int pos = 0;
    Link<E> link = voidLink.next;
    ....
}

再看看幾個(gè)重要的方法:

//會(huì)拋異常
//注意是從頭部移除
public E pop() {
    return removeFirstImpl();
}
//從頭部添加
public void push(E e) {
    addFirstImpl(e);
}
//從頭部移除元素
public E remove() {
    return removeFirstImpl();
}
//沒(méi)有元素返回null
public E poll() {
    return size == 0 ? null : removeFirst();
}
public E peek() {
    return peekFirstImpl();
}

只有poll peek操作遇到LinkedList無(wú)元素返回null毫蚓,其他做此類型會(huì)拋出異常,其實(shí)現(xiàn)的接口已有說(shuō)明

我們需要主要一下clear():僅是將size置零昔善,voidLink的next,previous指向空元素而已绍些,可見(jiàn)voidLink的作用很關(guān)鍵

public void clear() {
  if (size > 0) {
      size = 0;
      voidLink.next = voidLink;
      voidLink.previous = voidLink;
      modCount++;
  }

通過(guò)以上的分析,基本能理解好LinkedList了耀鸦。其他具體的方法就不一一貼出來(lái)了柬批,操作原理和上面的分析大同小異。
需要對(duì)每個(gè)方法詳細(xì)的理解袖订,可以直接去看LinkedList的源碼氮帐。

ArrayList LinkedList
基于數(shù)組實(shí)現(xiàn) 基于鏈表實(shí)現(xiàn)
實(shí)現(xiàn)RandomAccess接口,可以直接訪問(wèn)指定索引的元素 沒(méi)現(xiàn)RandomAccess接口,需要遍歷到指定索引的位置
查找訪問(wèn)快洛姑,插入刪除慢 查找訪問(wèn)慢上沐,插入刪除快
存在擴(kuò)容拷貝 沒(méi)有容量限制,無(wú)需擴(kuò)容楞艾,內(nèi)部會(huì)使用到ArrayList(addAll的時(shí)候)

均不是線程安全的参咙,并發(fā)操作會(huì)報(bào)異常

you KO LinkedList !!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市硫眯,隨后出現(xiàn)的幾起案子蕴侧,更是在濱河造成了極大的恐慌,老刑警劉巖两入,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件净宵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡裹纳,警方通過(guò)查閱死者的電腦和手機(jī)择葡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)剃氧,“玉大人敏储,你說(shuō)我怎么就攤上這事∨蟀埃” “怎么了已添?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵迫横,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我酝碳,道長(zhǎng)矾踱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任疏哗,我火速辦了婚禮呛讲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘返奉。我一直安慰自己贝搁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布芽偏。 她就那樣靜靜地躺著雷逆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪污尉。 梳的紋絲不亂的頭發(fā)上膀哲,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音被碗,去河邊找鬼某宪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锐朴,可吹牛的內(nèi)容都是我干的兴喂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼焚志,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衣迷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起酱酬,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤壶谒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后岳悟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體佃迄,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泼差,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年贵少,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堆缘。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滔灶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吼肥,到底是詐尸還是另有隱情录平,我是刑警寧澤麻车,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站斗这,受9級(jí)特大地震影響动猬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜表箭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一赁咙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧免钻,春花似錦彼水、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拆魏,卻和暖如春盯桦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渤刃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工俺附, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溪掀。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓事镣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親揪胃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子璃哟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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