LinkedList源碼分析

昨天大概看了ArrayList的源碼實(shí)現(xiàn)吴藻,其實(shí)還是比較簡(jiǎn)單的。主要就是操作內(nèi)部的數(shù)組首有,而用的也比較多的就是今天要看的LinkedList了抖棘,它相對(duì)于ArrayList具有更高的增加/刪除效率,適合作為經(jīng)常變動(dòng)的列表使用净捅,其讀取的時(shí)間復(fù)雜度為O(n)疑枯。而ArrayList的讀取時(shí)間復(fù)雜度為O(1),變動(dòng)多的話經(jīng)常會(huì)導(dǎo)致數(shù)組的復(fù)制操作蛔六,所以更適合作為不經(jīng)常變動(dòng)的列表荆永。

源碼分析

不得不提的Node節(jié)點(diǎn)

/**
 * 作為整個(gè)鏈表實(shí)現(xiàn)的核心封裝,Node節(jié)點(diǎn)作為一個(gè)靜態(tài)內(nèi)部類使用古今,每一個(gè)插入的數(shù)據(jù)元素都被Node節(jié)點(diǎn)包裹著
 */
private static class Node<E> {
        E item;
        Node<E> next; //指向下一個(gè)節(jié)點(diǎn)
        Node<E> prev; //指向上一個(gè)節(jié)點(diǎn)

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
之后所有關(guān)于LinkedList的操作其實(shí)都是關(guān)于Node的操作屁魏,理解Node節(jié)點(diǎn)就是理解LinkedList的基礎(chǔ),接下來(lái)我們還是從基本的類定義開始看捉腥。

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
AbstractSequentialList :為實(shí)現(xiàn)序列訪問(wèn)的數(shù)據(jù)儲(chǔ)存結(jié)構(gòu)的提供了所需要的最小化的接口實(shí)現(xiàn)(這接口主要是說(shuō)明他是希望實(shí)現(xiàn)者通過(guò)順序訪問(wèn)的)
Deque:雙向列表(就是支持鏈表兩頭操作的意思)

接口不是講解的重點(diǎn)氓拼,只有在需要的時(shí)候我才會(huì)去解讀接口中的具體方法含義,如果一個(gè)一個(gè)接口跟進(jìn)去的內(nèi)容比較繁瑣抵碟,而且意義也不大

屬性值

//transient 關(guān)鍵字表示在序列化的時(shí)候并不會(huì)隨著鏈表序列化為二進(jìn)制數(shù)存儲(chǔ)桃漾,可以這么做是因?yàn)榧词狗葱蛄谢鬀](méi)有這些值,
//但是在改變鏈表結(jié)構(gòu)的時(shí)候這些值又會(huì)重新生成拟逮,減少了不必要的字段也就減少了序列化的開銷撬统。
//源碼編寫者確實(shí)很精打細(xì)算。非常細(xì)微的地方都考慮到了
    transient int size = 0; //鏈表長(zhǎng)度
    transient Node<E> first; //鏈表頭節(jié)點(diǎn)
    transient Node<E> last; //鏈表尾節(jié)點(diǎn)

構(gòu)造函數(shù)

/**
 * 其中空的構(gòu)造函數(shù)我就不說(shuō)了敦迄。恋追。這個(gè)構(gòu)造函數(shù)就是直接將Collection中的數(shù)據(jù)插入到鏈表中
 */
public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index); //插入位置必須在[0,size]之間
        //為了便利方便所以轉(zhuǎn)換為數(shù)組
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        //succ:index所在位置的Node引用,pred:index所在位置的前一個(gè)Node引用
        Node<E> pred, succ;
        if (index == size) { //如果插入的位置就是鏈表末尾的話那就可以直接插入而不用移動(dòng)元素
            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) //說(shuō)明插入的位置是頭節(jié)點(diǎn)位置(原始的鏈表為空)
                first = newNode;
            else //上一句已經(jīng)將newNode的前節(jié)點(diǎn)指向pred,這次要將pred的后節(jié)點(diǎn)指向newNode了(注意是雙向認(rèn)定)
                pred.next = newNode;
            pred = newNode;//節(jié)點(diǎn)后移
        }

        if (succ == null) {
            last = pred; //注意經(jīng)過(guò)移動(dòng)之后現(xiàn)在pred已經(jīng)指向了新鏈表末尾的Node節(jié)點(diǎn)
        } else { //因?yàn)槭窃趕ucc這個(gè)節(jié)點(diǎn)原來(lái)的位置進(jìn)行插入的罚屋,所以現(xiàn)在succ其實(shí)已經(jīng)拋離整個(gè)鏈表了苦囱,需要跟新的前段鏈表重新連接一下
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++; //作用與ArrayList中的modCount一樣
        return true;
    }
/**
 * 即使一個(gè)簡(jiǎn)單的取下標(biāo)的方法都很精致,考慮的前半段還是后半段(返回節(jié)點(diǎn)一定非空)
 */
    Node<E> node(int index) {
        if (index < (size >> 1)) { //如果index在鏈表的前半段就從頭部開始查找脾猛,這里不用size/2是因?yàn)橐莆徊僮鞲?            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else { //如果index在鏈表的后半段就從尾部開始查找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

基本方法

public boolean add(E e) {
        linkLast(e);
        return true;
}
void linkLast(E e) {
        final Node<E> oldTail = last;
        final Node<E> newNode = new Node<>(oldTail , e, null);
        last = newNode; //這里其實(shí)只要注意一下原來(lái)的鏈表是不是空鏈表就行
        if (oldTail  == null)
            first = newNode;
        else
            oldTail.next = newNode; //沒(méi)有if判斷的話這里就報(bào)NPE了
        size++;
        modCount++;
    }
因?yàn)閘inkFirst跟linkLast一模一樣撕彤,所以我就不看了。

void linkBefore(E e, Node<E> succ) {
        // 因?yàn)檫@是一個(gè)內(nèi)部調(diào)用的方法猛拴,所以已經(jīng)保證succ不為null
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)//如果succ是頭結(jié)點(diǎn)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

private E unlinkFirst(Node<E> f) {
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // 是f沒(méi)有其他的引用鏈羹铅,促進(jìn)垃圾回收
        first = next;
        if (next == null) //如果原來(lái)鏈表只有一個(gè)節(jié)點(diǎn)的話
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

E unlink(Node<E> x) {
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        if (prev == null) { //如果x節(jié)點(diǎn)是頭結(jié)點(diǎn)
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        if (next == null) { //如果x節(jié)點(diǎn)是尾節(jié)點(diǎn)
            last = prev;
        } else {
            next.prev = prev;
            x.next = null; //取消x節(jié)點(diǎn)對(duì)于前后節(jié)點(diǎn)的引用
        }
        x.item = null;//還是使用置空的方式促進(jìn)GC
        size--;
        modCount++;
        return element;
    }
現(xiàn)在為止基本的變更操作已經(jīng)介紹完了蚀狰,像removeLast,removeFirst职员,addFirst麻蹋,addLast等等這些方法其實(shí)只要明白上面操作就完全夠了。還有一些poll廉邑,take等方法都是基于以上的方法所講哥蔚,基本沒(méi)有其他內(nèi)容倒谷,就是有的報(bào)錯(cuò)有的不報(bào)錯(cuò)之類的差別蛛蒙。看一下其他典型的方法
public boolean removeLastOccurrence(Object o) {
//這里的核心內(nèi)容就是判斷所移除的對(duì)象是不是null渤愁,因?yàn)槭莕ull的話就沒(méi)法使用equals方法比較
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
//無(wú)參的toArray()沒(méi)啥可看的牵祟,就是遍歷然后賦值就可以
public <T> T[] toArray(T[] a) {
        if (a.length < size) //如果a轉(zhuǎn)不下鏈表內(nèi)容的話就創(chuàng)建一個(gè)包含T類型元素的數(shù)組
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size) //注意這里的置空操作
            a[size] = null;

        return a;
    }

public Object clone() {
        LinkedList<E> clone = superClone();
        clone.first = clone.last = null;
        clone.size = 0;
        clone.modCount = 0;
       //從這里看到這個(gè)克隆還是淺克隆,因?yàn)閤.item還是原來(lái)的地址
        for (Node<E> x = first; x != null; x = x.next)
            clone.add(x.item);
        return clone;
    }

接下來(lái)我們看一下Iterator的詳細(xì)實(shí)現(xiàn),通常遍歷有兩種方式抖格,一種是通過(guò)for循環(huán)進(jìn)行遍歷诺苹,另外一種就是通過(guò)Iterator了,而且Iterator可以在遍歷的過(guò)程中改變鏈表節(jié)點(diǎn)雹拄,但是在for循環(huán)過(guò)程中改變鏈表節(jié)點(diǎn)的話會(huì)有你喜歡的東西出現(xiàn)噢收奔。。

private class ListItr implements ListIterator<E> //內(nèi)部類ListItr
private Node<E> lastReturned = null; //記錄本次操作的目標(biāo)對(duì)象(next()返回是它滓玖,刪除的話也是它)
private Node<E> next; //下一個(gè)遍歷出的節(jié)點(diǎn)
private int nextIndex; //下一個(gè)節(jié)點(diǎn)的下標(biāo)
private int expectedModCount = modCount; //用來(lái)判斷在遍歷過(guò)程中有沒(méi)有節(jié)點(diǎn)變更

ListItr(int index) {//初始化的時(shí)候從下標(biāo)為index的位置開始
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }
//下面這兩個(gè)方法應(yīng)該是我們最常調(diào)用的方法了
public boolean hasNext() {
            return nextIndex < size;
        }
//從這里我們就知道m(xù)odCount的用途了坪哄,如果是for循環(huán)的話,循環(huán)過(guò)程中改變節(jié)點(diǎn)會(huì)改變modCount.
//因?yàn)閒or循環(huán)本質(zhì)上還是靠Iterator來(lái)實(shí)現(xiàn)的势篡,所以在遍歷過(guò)程中發(fā)現(xiàn)節(jié)點(diǎn)變化就立馬報(bào)錯(cuò)了
public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();
            lastReturned = next; //這個(gè)lastReturned主要是用來(lái)保存返回結(jié)果的
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
//這就解釋了為什么遍歷過(guò)程之中Iteraort的remove是可以的
public void remove() {
            checkForComodification(); //操作開始時(shí)候的modCount與expectedModCount的相等還是要保證的
            if (lastReturned == null)
                throw new IllegalStateException();
            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
//正常情況兩者是不會(huì)相等的翩肌,但是為了保證代碼的健壯性我們要考慮這種操作,例如我在多線程情況下進(jìn)行操作禁悠,調(diào)用next()方法到了
//第四行時(shí)候忽然切到另外一個(gè)線程念祭,這時(shí)候其實(shí)next = lastReturned而且由于next()還沒(méi)有運(yùn)行到nextIndex++所以這里也就不用nextIndex--
//雖然LinkedList不是線程安全的,但是在這里盡量去保證其在多線程運(yùn)行環(huán)境下的安全性
            if (next == lastReturned) 
                next = lastNext;
            else
                nextIndex--; //刪除了一個(gè)next前的元素,nextIndex--是必要的
            lastReturned = null;
            expectedModCount++; //在unlink中modCount++了碍侦,所以expectedModCount在這里也自增
        }
public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null) //如果插入位置在鏈表末尾的話
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

當(dāng)然LinkedList內(nèi)部還有一個(gè)DescendingIterator作為逆向遍歷使用的粱坤,因?yàn)長(zhǎng)inkedList是雙向鏈表,所以不管正向還是逆向其實(shí)內(nèi)部實(shí)現(xiàn)都是一樣的瓷产,不需要再進(jìn)行過(guò)多的關(guān)注了站玄。
<strong>到這里其實(shí)LinkedList的整個(gè)實(shí)現(xiàn)都已經(jīng)講解清楚了,沒(méi)有提到的方法往往是因?yàn)樘^(guò)簡(jiǎn)單或者內(nèi)容重復(fù)拦英,凡是我覺(jué)得值得注意的地方我都進(jìn)行了清晰的注釋蜒什,如果有哪些大家覺(jué)得我理解有誤的地方一定要在評(píng)論的時(shí)候?qū)懗鰜?lái),我會(huì)積極參與討論疤估。</strong>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灾常,一起剝皮案震驚了整個(gè)濱河市霎冯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钞瀑,老刑警劉巖沈撞,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雕什,居然都是意外死亡缠俺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門贷岸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)壹士,“玉大人,你說(shuō)我怎么就攤上這事偿警□锞龋” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵螟蒸,是天一觀的道長(zhǎng)盒使。 經(jīng)常有香客問(wèn)我,道長(zhǎng)七嫌,這世上最難降的妖魔是什么少办? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诵原,結(jié)果婚禮上英妓,老公的妹妹穿的比我還像新娘。我一直安慰自己皮假,他們只是感情好鞋拟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惹资,像睡著了一般贺纲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上褪测,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天猴誊,我揣著相機(jī)與錄音,去河邊找鬼侮措。 笑死懈叹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的分扎。 我是一名探鬼主播澄成,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了墨状?” 一聲冷哼從身側(cè)響起卫漫,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肾砂,沒(méi)想到半個(gè)月后列赎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镐确,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年包吝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片源葫。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诗越,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臼氨,到底是詐尸還是另有隱情掺喻,我是刑警寧澤芭届,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布储矩,位于F島的核電站,受9級(jí)特大地震影響褂乍,放射性物質(zhì)發(fā)生泄漏持隧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一逃片、第九天 我趴在偏房一處隱蔽的房頂上張望屡拨。 院中可真熱鬧,春花似錦褥实、人聲如沸呀狼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哥艇。三九已至,卻和暖如春僻澎,著一層夾襖步出監(jiān)牢的瞬間貌踏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工窟勃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祖乳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓秉氧,卻偏偏與公主長(zhǎng)得像眷昆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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

  • 一.線性表 定義:零個(gè)或者多個(gè)元素的有限序列亚斋。也就是說(shuō)它得滿足以下幾個(gè)條件:??①該序列的數(shù)據(jù)元素是有限的垦藏。??②...
    Geeks_Liu閱讀 2,701評(píng)論 1 12
  • 概述 在分析LinkedList的源碼之前,先看一下ArrayList在數(shù)據(jù)結(jié)構(gòu)中的位置伞访,常見的數(shù)據(jù)結(jié)構(gòu)按照邏輯結(jié)...
    wustor閱讀 595評(píng)論 0 0
  • Java中常用到ArrayList和LinkedList掂骏,面試中也常問(wèn)到兩者的區(qū)別,各自的使用場(chǎng)景厚掷。要想清楚的明白...
    昵稱全尼馬被注冊(cè)了閱讀 1,186評(píng)論 1 15
  • LinkedList 認(rèn)識(shí) LinkedList是一種雙向鏈表弟灼,雙向鏈表我認(rèn)為有兩點(diǎn)含義: 鏈表中任意一個(gè)存儲(chǔ)單元...
    zlb閱讀 240評(píng)論 0 0
  • 整體介紹 LinkedList同時(shí)實(shí)現(xiàn)了List接口和Deque接口,也就是說(shuō)它既可以看作一個(gè)順序容器冒黑,又可以看作...
    SeaRise閱讀 317評(píng)論 0 0