LinkedList源碼分析

經(jīng)常為了面試要記住LinkedList和ArrayList的區(qū)別,比如存取速度的比較。如果不了解他們的實(shí)現(xiàn)方式,不看源碼,記住了也是死記硬背,容易忘記,沒(méi)有太大的意義。這里講的是LinkedList,暫時(shí)不講ArrayList,從源碼了解LinkedList的實(shí)現(xiàn)方式以及操作方法的實(shí)現(xiàn),才能更全面了解LinkedList。

LinkedList是基于雙向鏈表的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,所以必須要知道掌握雙向鏈表這種數(shù)據(jù)結(jié)構(gòu),如果對(duì)雙向鏈表不了解的話得先去學(xué)一下,比較容易理解的一種數(shù)據(jù)結(jié)構(gòu)。學(xué)習(xí)了雙向鏈表看LinkedList的源碼會(huì)更輕松一些,從 源碼了解LinkedList的性質(zhì),才能更加了解LinkedList,知道在什么場(chǎng)合更加適合使用LinkedList。別啰嗦了,看源碼:

public class LinkedList<E> extends AbstractSequentialList<E> implements
    List<E>, Deque<E>, Queue<E>, Cloneable, Serializable {
     ....
}

LinkedList可以指定一個(gè)泛型(一般我們都會(huì)這么做),具有多個(gè)實(shí)現(xiàn),源碼大部分邏輯都是在實(shí)現(xiàn)這些接口的方法,從它們的名字我們可以看出除了可以當(dāng)作鏈表來(lái)操作外,它還可以當(dāng)作棧,隊(duì)列和雙端隊(duì)列來(lái)使用。

來(lái)看構(gòu)造函數(shù)

//記錄LinkedList的大小,即LinkedList有多少個(gè)節(jié)點(diǎn)(或者說(shuō)元素)
transient int size = 0;   
//一個(gè)空的節(jié)點(diǎn)
transient Link<E> voidLink;

/**
 * Constructs a new instance of {@code LinkedList} that holds all of the
 * elements contained in the specified {@code collection}. The order of the
 * elements in this new {@code LinkedList} will be determined by the
 * iteration order of {@code collection}.
 *
 * @param collection
 *            the collection of elements to add.
 */
//這個(gè)帶參數(shù)的構(gòu)造函數(shù)的this()方法是調(diào)用下面的那個(gè)構(gòu)造函數(shù),之后將參數(shù)collection跟voidLink(一個(gè)空節(jié)點(diǎn),在第二個(gè)構(gòu)造函數(shù)里創(chuàng)建出來(lái)的) 
//鏈接起來(lái),addAll(collection)后面講解
public LinkedList(Collection<? extends E> collection) {
    this();
    addAll(collection);
}

/**
 * Constructs a new empty instance of {@code LinkedList}(創(chuàng)建一個(gè)空的實(shí)例)
 */
public LinkedList() {
    voidLink = new Link<E>(null, null, null);
    voidLink.previous = voidLink;
    voidLink.next = voidLink;
}

//創(chuàng)建節(jié)點(diǎn)的靜態(tài)內(nèi)部類
private static final class Link<ET> {
    //ET其實(shí)就是E這個(gè)泛型,從上面的構(gòu)造函數(shù)可以看出,這個(gè)字段就是元素
    ET data;
    //這兩個(gè)字段分別是當(dāng)前節(jié)點(diǎn)的頭和尾,分布存儲(chǔ)的是前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn),雙向鏈表數(shù)據(jù)結(jié)構(gòu)的每個(gè)節(jié)點(diǎn)都是有這三個(gè)東西組成的
    Link<ET> previous, next;

    Link(ET o, Link<ET> p, Link<ET> n) {
        data = o;
        previous = p;
        next = n;
    }
}

從第二個(gè)構(gòu)造函數(shù)我們知道new出一個(gè)LinkedList的時(shí)候,這個(gè)實(shí)例里面只有一個(gè)空的節(jié)點(diǎn)voidLink ,一般我們會(huì)有個(gè)add(int location, E object)函數(shù)添加元素到指定位置,所以接著看add(E object)函數(shù)添加元素到最后一個(gè)節(jié)點(diǎn)的后面,addAll(int location, Collection<? extends E> collection)函數(shù)添加Collection到指定位置,addAll(Collection<? extends E> collection)函數(shù)添加Collection到最后一個(gè)節(jié)點(diǎn)的后面。addFirst(E object)添加元素到第一個(gè)節(jié)點(diǎn)的前面,addLast(E object)添加元素到最后一個(gè)節(jié)點(diǎn)后面。這些是所有添加元素的方法。分別看下他們的邏輯,都是相似的。理解了其中一個(gè)其他都好理解。

 /**
 * Inserts the specified object into this {@code LinkedList} at the
 * specified location. The object is inserted before any previous element at
 * the specified location. If the location is equal to the size of this
 * {@code LinkedList}, the object is added at the end.
 *上面的意思就是將指定的object 插入到指定的位置,之前位置的object 以及它后面的object 的位置的都會(huì)加1
 *注意最后一句話的意思是如果這個(gè)LinkedList的size等于location ,那么意思就是插入到最后一個(gè)位置。不允許location >size,location<0
 *
 * @param location
 *            the index at which to insert.
 * @param object
 *            the object to add.
 * @throws IndexOutOfBoundsException
 *             if {@code location < 0 || location > size()}
 */
@Override
public void add(int location, E object) {
    if (location >= 0 && location <= size) {
        Link<E> link = voidLink;
        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;
            }
        }
        Link<E> previous = link.previous;
        Link<E> newLink = new Link<E>(object, previous, link);
        previous.next = newLink;
        link.previous = newLink;
        size++;
        modCount++;
    } else {
        throw new IndexOutOfBoundsException();
    }
}

方法注釋可以了解到大概,分析代碼才能更細(xì)節(jié)的了解,可以看到location 必須大于等于0且小于等于sise,否則會(huì)拋出異常,滿足條件后需要?jiǎng)?chuàng)建一個(gè)節(jié)點(diǎn)引用,先指向voidLink這個(gè)空節(jié)點(diǎn),因?yàn)槲覀冃枰柚鷙oidLink和size才能找到一個(gè)目標(biāo)節(jié)點(diǎn),該目標(biāo)節(jié)點(diǎn)起著關(guān)鍵的作用,先看if和else語(yǔ)句,判斷插入位置區(qū)域鏈表的哪一邊,如果是左邊的話則進(jìn)入if條件的for循環(huán)從第一個(gè)位置開(kāi)始找目標(biāo)節(jié)點(diǎn),首先我們必須知道voidLink.next永遠(yuǎn)的是當(dāng)前的第一個(gè)節(jié)點(diǎn),等看完了所有添加元素的函數(shù)邏輯的時(shí)候就明白了。如果進(jìn)入else條件,邏輯也是一樣的,只是從尾部開(kāi)始查找目標(biāo)節(jié)點(diǎn)。找到目標(biāo)節(jié)點(diǎn)之后就很簡(jiǎn)單了,把目標(biāo)節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的next指向newLink (新插入節(jié)點(diǎn)),目標(biāo)節(jié)點(diǎn)的previous 指向newLink ,這樣就完成了拼接,目標(biāo)節(jié)點(diǎn)的位置被新節(jié)點(diǎn)占據(jù)著,著目標(biāo)節(jié)點(diǎn)以及后面的節(jié)點(diǎn)的位置都各自加1,此時(shí)我們還要加size+1,modCount+1只是一個(gè)統(tǒng)計(jì)修改的次數(shù)。邏輯還是比較簡(jiǎn)單的,后面的幾個(gè)添加元素的方法都是大同小異的。

add(E object)在尾部添加節(jié)點(diǎn),更加簡(jiǎn)單目標(biāo)節(jié)點(diǎn)哦度不用找,因?yàn)関oidLink.previous就是最后一個(gè)節(jié)點(diǎn),跟voidLink.next永遠(yuǎn)的是當(dāng)前的第一個(gè)節(jié)點(diǎn)永遠(yuǎn)是第一個(gè)節(jié)點(diǎn)相呼應(yīng):

 /**
 * Adds the specified object at the end of this {@code LinkedList}.
 *
 * @param object
 *            the object to add.
 * @return always true
 */
@Override
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;
}

其他幾個(gè)添加元素的函數(shù)都是按照這種套路執(zhí)行的,不必每一個(gè)都進(jìn)行分析了,但每個(gè)人都有必要去看一遍的。

與添加對(duì)應(yīng)的方法就是刪除了,remove(int location),removeFirst(),removeLast(),方法名可以看出什么意思了,刪除的套路前半部分也是一樣的,目標(biāo)節(jié)點(diǎn),然后根據(jù)目標(biāo)節(jié)點(diǎn)找到他前后的節(jié)點(diǎn),讓前一個(gè)節(jié)點(diǎn)的next指向后一個(gè)節(jié)點(diǎn),后一個(gè)節(jié)點(diǎn)的previous指向前一個(gè)節(jié)點(diǎn)。還有一個(gè)remove(Object object)方法收回復(fù)雜一些,后面單獨(dú)講,先看remove(int location)(removeFirst(),removeLast()與removeFirst(),removeLast()套路相似)

/**
 * Removes the object at the specified location from this {@code LinkedList}.
 *
 * @param location
 *            the index of the object to remove
 * @return the removed object
 * @throws IndexOutOfBoundsException
 *             if {@code location < 0 || location >= size()}
 */
@Override
public E remove(int location) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;
        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;
            }
        }
        Link<E> previous = link.previous;
        Link<E> next = link.next;
        previous.next = next;
        next.previous = previous;
        size--;
        modCount++;
        return link.data;
    }
    throw new IndexOutOfBoundsException();
}

remove(Object object)源碼

@Override
public boolean remove(Object object) {
    return removeFirstOccurrenceImpl(object);
}

 private boolean removeFirstOccurrenceImpl(Object o) {
    Iterator<E> iter = new LinkIterator<E>(this, 0);
    return removeOneOccurrence(o, iter);
}

 private boolean removeOneOccurrence(Object o, Iterator<E> iter) {
    while (iter.hasNext()) {
        E element = iter.next();
        if (o == null ? element == null : o.equals(element)) {
            iter.remove();
            return true;
        }
    }
    return false;
}

從源碼看到的調(diào)用到最后removeOneOccurrence(Object o, Iterator<E> iter)函數(shù)的iter.remove()防止執(zhí)行了移除工作,這個(gè)方法里面調(diào)用了LinkIterator的hasNext()、next()、remove()三個(gè)方法,進(jìn)去看他們的源碼看下到底做了什么工作

public boolean hasNext() {
        return link.next != list.voidLink;
    }


 public ET next() {
        if (expectedModCount == list.modCount) {
            LinkedList.Link<ET> next = link.next;
            if (next != list.voidLink) {
                lastLink = link = next;
                pos++;
                return link.data;
            }
            throw new NoSuchElementException();
        }
        throw new ConcurrentModificationException();
    }

public void remove() {
        if (expectedModCount == list.modCount) {
            if (lastLink != null) {
                Link<ET> next = lastLink.next;
                Link<ET> previous = lastLink.previous;
                next.previous = previous;
                previous.next = next;
                if (lastLink == link) {
                    pos--;
                }
                link = previous;
                lastLink = null;
                expectedModCount++;
                list.size--;
                list.modCount++;
            } else {
                throw new IllegalStateException();
            }
        } else {
            throw new ConcurrentModificationException();
        }
    }

hasNext()判斷當(dāng)前節(jié)點(diǎn)(就是voidLink,從構(gòu)造函數(shù)看出來(lái)的)的下一個(gè)節(jié)點(diǎn)是否為空,next()返回下一個(gè)節(jié)點(diǎn)的data,remove()方法終于是我們熟悉的背影了,主要邏輯就在這里了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,653評(píng)論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,063評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,835評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,235評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,459評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,819評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,004評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,257評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,676評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,937評(píng)論 1 288
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,717評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,003評(píng)論 2 374

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