Java集合(三) —— LinkedList源碼分析

Java集合(一) —— Collection源碼分析
Java集合(二) —— ArrayList源碼分析
Java集合(三) —— LinkedList源碼分析
Java集合(四) —— PriorityQueue源碼分析
Java集合(五) —— HashSet源碼分析
Java集合(六) —— LinkedHashSet源碼分析
Java集合(七) —— TreeSet源碼分析
Java集合(八) —— HashMap源碼分析
Java集合(九) —— LinkedHashMap源碼分析
Java集合(十) —— TreeMap源碼分析
因?yàn)楸硎龇奖銌栴}宋渔,有時(shí)使用前驅(qū)&后繼節(jié)點(diǎn)凸郑,有時(shí)使用前驅(qū)&后繼指針,事實(shí)上對(duì)象保存的就是對(duì)象的地址或衡,也就是指針。

1.LinkedList繼承關(guān)系

LinkedList.png

2.LinkedList的數(shù)據(jù)結(jié)構(gòu)

private static class Node<E> {
    E item;
    Node<E> next; // 前驅(qū)節(jié)點(diǎn)
    Node<E> prev; // 后繼節(jié)點(diǎn)

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

LinkedList繼承自AbstractSequentialList并實(shí)現(xiàn)了List接口车遂,底層是雙向鏈表數(shù)據(jù)結(jié)構(gòu)封断;LinkedList同時(shí)實(shí)現(xiàn)了Deque接口,可用作雙向隊(duì)列舶担。

3.LinkedList性能分析

1.LinkedList是基于鏈表實(shí)現(xiàn)的坡疼,不存在容量不足的問題,所以沒有擴(kuò)容方法衣陶,也就沒有因擴(kuò)容帶來的性能問題柄瑰。
2.LinkedList查找元素時(shí)闸氮,根據(jù)index的大小需要從開始或從結(jié)尾遍歷到index位置,其時(shí)間復(fù)雜度為O(n)教沾。
3.LinkedList新增/刪除元素時(shí)蒲跨,不存在數(shù)據(jù)的移動(dòng),只需要修改前驅(qū)節(jié)點(diǎn)與后繼節(jié)點(diǎn)的指向授翻,不考慮查找的情況下或悲,其時(shí)間復(fù)雜度為O(1)。
總結(jié)(對(duì)比ArrayList):

  • LinkedList在查找數(shù)據(jù)時(shí)需要遍歷鏈表堪唐,性能較差巡语;而新增或刪除只需要修改指針的指向,具有較好的性能淮菠,所以LinkedList常用于查詢較少男公,新增/刪除較多的場(chǎng)景。
  • ArrayList具有較好的查詢性能合陵,而新增/刪除都可能引起大量元素的移動(dòng)枢赔,性能較差,所以ArrayList適用于查詢較多曙寡,新增/刪除較少的場(chǎng)景糠爬。
  • LinkedList需要額外的內(nèi)存空間保存指針(前向指針(前向節(jié)點(diǎn))/后繼指針(后繼節(jié)點(diǎn)))信息,如果對(duì)內(nèi)存有較高要求举庶,可以考慮其他方案执隧。
  • 盡量不要使用for循環(huán)遍歷LinkedList,應(yīng)該使用迭代器户侥,因?yàn)長(zhǎng)inkedList每訪問一個(gè)數(shù)據(jù)都要遍歷鏈表一次(具體看源碼分析)镀琉。

4.LinkedList源碼分析

1.成員變量分析

// 鏈表的大小
transient int size = 0;
// 第一個(gè)節(jié)點(diǎn)(頭結(jié)點(diǎn))
transient Node<E> first;
// 最后一個(gè)節(jié)點(diǎn)(尾結(jié)點(diǎn))
transient Node<E> last;

2.構(gòu)造方法分析

// .. 沒啥好說的
public LinkedList() {
}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

3.核心方法分析

3.1新增元素

1.從鏈表尾部新增一個(gè)節(jié)點(diǎn)

public boolean add(E e) {
    linkLast(e);
    return true;
}
/**
 * 從鏈表尾部添加一個(gè)新節(jié)點(diǎn)
 */
void linkLast(E e) {
    // 定義一個(gè)臨時(shí)變量保存最后一個(gè)節(jié)點(diǎn)
    final Node<E> l = last;
    // 創(chuàng)建一個(gè)新的節(jié)點(diǎn),l == 上一個(gè)節(jié)點(diǎn) 蕊唐; null == 下一個(gè)節(jié)點(diǎn)(最后一個(gè)節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為null)
    final Node<E> newNode = new Node<>(l, e, null);
    // 將新建的節(jié)點(diǎn)置為最后一個(gè)節(jié)點(diǎn)
    last = newNode;
    if (l == null)
        // 如果 l == null表示之前一個(gè)節(jié)點(diǎn)都沒有屋摔,新增節(jié)點(diǎn)之后first也指向新節(jié)點(diǎn)
        first = newNode;
    else
        // 否則,之前的最后一個(gè)節(jié)點(diǎn)的后繼指針指向新建的節(jié)點(diǎn)
        l.next = newNode;
    // 鏈表大小+1
    size++;
    // 修改次數(shù)+1
    modCount++;
}

2.指定位置新增節(jié)點(diǎn)

/**
 * 指定位置新增元素
 */
public void add(int index, E element) {
    // 檢查是否越界
    checkPositionIndex(index);

    if (index == size)
        // 鏈表最后新增節(jié)點(diǎn)
        linkLast(element);
    else
        // 查找指定下標(biāo)節(jié)點(diǎn)替梨,在指定下標(biāo)之前新增節(jié)點(diǎn)
        linkBefore(element, node(index));
}

/**
 * 查找節(jié)點(diǎn)的方法
 * 這是LinkedList一個(gè)很重要的方法钓试,LinkedList很多方法都要依賴此方法查找對(duì)應(yīng)的節(jié)點(diǎn)
 */
Node<E> node(int index) {
    // 判斷index的大小,決定要從頭開始遍歷鏈表的一半還是從尾部開始遍歷鏈表的一半
    if (index < (size >> 1)) {
        // 順序遍歷查找第index個(gè)節(jié)點(diǎn)并返回
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // 倒序查找第index個(gè)節(jié)點(diǎn)并返回
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

/**
 * 指定位置插入新的節(jié)點(diǎn)
 */
void linkBefore(E e, Node<E> succ) {
    // succ:指定位置節(jié)點(diǎn)
    // pred保存指定位置節(jié)點(diǎn)的前向指針
    final Node<E> pred = succ.prev;
    // 創(chuàng)建一個(gè)新的節(jié)點(diǎn)
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 指定位置的節(jié)點(diǎn)的前向指針指向新建的節(jié)點(diǎn)
    succ.prev = newNode;
    if (pred == null)
        // 如果pred == null副瀑,表示原來鏈表為空或者剛好從第一個(gè)節(jié)點(diǎn)插入新的節(jié)點(diǎn)(因?yàn)榈谝粋€(gè)節(jié)點(diǎn)沒有前向節(jié)點(diǎn)弓熏,pred = null)
        first = newNode;
    else
        // 否則,指定位置節(jié)點(diǎn)的前向節(jié)點(diǎn)的后繼指針指向新建的節(jié)點(diǎn)(有些繞口糠睡,自己理解吧)
        pred.next = newNode;
    size++; // 鏈表大小+1
    modCount++; // 修改次數(shù)+1
}

3.批量插入數(shù)據(jù)

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

/**
 * 批量插入數(shù)據(jù)
 */
public boolean addAll(int index, Collection<? extends E> c) {
    // 校驗(yàn)是否越界
    checkPositionIndex(index);

    // 將集合轉(zhuǎn)為數(shù)組
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    Node<E> pred, succ;
    if (index == size) {
        // 從鏈表尾部新增節(jié)點(diǎn)
        succ = null;
        pred = last;
    } else {
        // 指定位置節(jié)點(diǎn)
        succ = node(index);
        // 記錄指定位置節(jié)點(diǎn)的前向節(jié)點(diǎn)
        pred = succ.prev;
    }

    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            // 從頭部新增節(jié)點(diǎn)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    if (succ == null) {
        // 如果是從尾部新增的節(jié)點(diǎn)挽鞠,就將原先最后一個(gè)節(jié)點(diǎn)指向新增元素之后的最后一個(gè)節(jié)點(diǎn)
        last = pred;
    } else {
        // 如果是從其他某個(gè)位置新增的節(jié)點(diǎn),就將新增的最后的一個(gè)節(jié)點(diǎn)的后繼指針指向指定位置節(jié)點(diǎn)(succ);指定位置節(jié)點(diǎn)的前向指針指向新增的最后一個(gè)節(jié)點(diǎn)
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}

3.2查找元素

1.查找數(shù)據(jù)

public E get(int index) {
    // 檢查是否越界
    checkElementIndex(index);
    // 返回指定節(jié)點(diǎn)的元素
    return node(index).item;
}

關(guān)于node方法之前已經(jīng)說過信认,這里不再重復(fù)材义,但是再說一遍,該方法很重要<奚汀F涞唷!

3.3修改元素

1.修改元素

public E set(int index, E element) {
    checkElementIndex(index);
    // 查找指定位置的Node節(jié)點(diǎn)(又是熟悉的node方法)
    Node<E> x = node(index);
    // 記錄節(jié)點(diǎn)上的元素
    E oldVal = x.item;
    // 將舊元素指向新元素
    x.item = element;
    // 返回舊元素
    return oldVal;
}

3.4刪除元素

1.根據(jù)索引刪除

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
E unlink(Node<E> x) {
    // 節(jié)點(diǎn)元素
    final E element = x.item;
    // 后繼節(jié)點(diǎn)
    final Node<E> next = x.next;
    // 前驅(qū)節(jié)點(diǎn)
    final Node<E> prev = x.prev;

    if (prev == null) {
        // prev == null表示刪除的x是頭結(jié)點(diǎn)
        first = next;
    } else {
        // 否則橄教,前驅(qū)節(jié)點(diǎn)的后繼指針指向x的后繼節(jié)點(diǎn)
        prev.next = next;
        // 將x的前驅(qū)節(jié)點(diǎn)置為null
        x.prev = null;
    }

    if (next == null) {
        // next == null清寇,說明刪除的x的尾結(jié)點(diǎn)
        last = prev;
    } else {
        // 否則喘漏,后繼節(jié)點(diǎn)的前驅(qū)指針指向x的前驅(qū)節(jié)點(diǎn)
        next.prev = prev;
        // 將x的后繼節(jié)點(diǎn)置為null
        x.next = null;
    }
    // 將x節(jié)點(diǎn)中的元素item置為null护蝶,此時(shí)達(dá)到刪除一個(gè)節(jié)點(diǎn)的目的
    x.item = null;
    size--;
    modCount++;
    // 返回移除的元素
    return element;
}

2.根據(jù)元素刪除

public boolean remove(Object o) {
    // 無論o是否為null,最終都是調(diào)用unlink(x)方法進(jìn)行刪除
    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;
}

3.批量刪除

/**
 * LinkedList并沒有該方法翩迈,該方法是定義在其父類AbstractCollection上的
 */
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    boolean modified = false;
    // 迭代器
    Iterator<?> it = iterator();
    while (it.hasNext()) {
        // 判斷迭代的元素是否包含在容器c中
        if (c.contains(it.next())) {
            // 真正刪除元素的方法
            it.remove();
            modified = true;
        }
    }
    return modified;
}

/**
 * AbstractSequentialList復(fù)寫父類的方法
 */
public Iterator<E> iterator() {
    return listIterator();
}

public ListIterator<E> listIterator() {
    return listIterator(0);
}

/**
 * LinkedList復(fù)寫父類的方法
 */
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    // ListItr初始化時(shí)持灰,將操作計(jì)數(shù)器modCount賦值給expectedModCount。
    // 之后每次調(diào)用remove方法都會(huì)校驗(yàn)expectedModCount與modCount是否相等负饲,否則會(huì)拋出異常堤魁。
    private int expectedModCount = modCount;
    // ...
}

/**
 * 調(diào)用next方法,迭代到下一個(gè)節(jié)點(diǎn)
 */
public E next() {
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();

    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

public void remove() {
    checkForComodification();
    if (lastReturned == null)
        throw new IllegalStateException();
    // lastReturned的下一個(gè)節(jié)點(diǎn)
    Node<E> lastNext = lastReturned.next;
    // 刪除lastReturned節(jié)點(diǎn)
    unlink(lastReturned);
    if (next == lastReturned)
        next = lastNext;
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

final void checkForComodification() {
    // 校驗(yàn)modCount與expectedModCount
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

5.關(guān)于LinkedList作為雙向隊(duì)列

前面已經(jīng)說過LinkedList實(shí)現(xiàn)了Deque接口返十,可以作為雙向隊(duì)列使用
1.從鏈表頭部或尾部新增節(jié)點(diǎn)

// 作為雙向隊(duì)列從頭部或尾部新增節(jié)點(diǎn)
public void addFirst(E e) {
    linkFirst(e);
}

/**
 * 從頭部新增節(jié)點(diǎn)
 */
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        // 如果是空鏈表妥泉,則新增的節(jié)點(diǎn)既是第一個(gè)節(jié)點(diǎn)又是最后一個(gè)節(jié)點(diǎn)
        last = newNode;
    else
        // 否則節(jié)點(diǎn)f的前向指針指向新增的節(jié)點(diǎn)
        f.prev = newNode;
    size++;
    modCount++;
}

/**
 * 從尾部新增節(jié)點(diǎn)
 */
public void addLast(E e) {
    linkLast(e);
}
/**
 * 前面分析過,不再贅述
 */
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++;
}

2.從頭部或尾部獲取元素

// 過于簡(jiǎn)單不做分析
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

3.從頭部或尾部刪除元素

/**
 * 從頭部刪除節(jié)點(diǎn)
 */
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    // 記錄f的下一個(gè)節(jié)點(diǎn)
    final Node<E> next = f.next;
    // 置為null洞坑,表示刪除節(jié)點(diǎn)
    f.item = null;
    f.next = null; // help GC
    // 使next成為第一個(gè)節(jié)點(diǎn)
    first = next;
    if (next == null)
        // 刪除的是最后一個(gè)節(jié)點(diǎn)盲链,則last也置為null
        last = null;
    else
        // 否則next節(jié)點(diǎn)的前向指針置為null(頭結(jié)點(diǎn)的前向指針與尾結(jié)點(diǎn)的后繼指針為null)
        next.prev = null;
    size--;
    modCount++;
    return element;
}

/**
 * 從尾部刪除節(jié)點(diǎn)
 * 與從頭部刪除節(jié)點(diǎn)的邏輯類似
 */
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

6.總結(jié)

1.LinkedList是雙向鏈表的數(shù)據(jù)結(jié)構(gòu),相比于單鏈表迟杂,在刪除一個(gè)節(jié)點(diǎn)時(shí)刽沾,不需要像單鏈表一樣一路保存當(dāng)前節(jié)點(diǎn)的前向節(jié)點(diǎn),因?yàn)殡p向鏈表當(dāng)前節(jié)點(diǎn)本身就已經(jīng)保存有前向指針排拷,所以刪除時(shí)雙向鏈表比單向鏈表效率更高侧漓。
2.需要額外的內(nèi)存空間保存節(jié)點(diǎn)信息。
3.對(duì)于大數(shù)據(jù)量來說监氢,相比ArrayList布蔗,具有增刪快查找慢特性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浪腐,一起剝皮案震驚了整個(gè)濱河市纵揍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牛欢,老刑警劉巖骡男,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異傍睹,居然都是意外死亡隔盛,警方通過查閱死者的電腦和手機(jī)犹菱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吮炕,“玉大人腊脱,你說我怎么就攤上這事×祝” “怎么了陕凹?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鳄炉。 經(jīng)常有香客問我杜耙,道長(zhǎng),這世上最難降的妖魔是什么拂盯? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任佑女,我火速辦了婚禮,結(jié)果婚禮上谈竿,老公的妹妹穿的比我還像新娘团驱。我一直安慰自己,他們只是感情好空凸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布嚎花。 她就那樣靜靜地躺著,像睡著了一般呀洲。 火紅的嫁衣襯著肌膚如雪紊选。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天两嘴,我揣著相機(jī)與錄音丛楚,去河邊找鬼。 笑死憔辫,一個(gè)胖子當(dāng)著我的面吹牛趣些,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贰您,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼坏平,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了锦亦?” 一聲冷哼從身側(cè)響起舶替,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杠园,沒想到半個(gè)月后顾瞪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年陈醒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惕橙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钉跷,死狀恐怖弥鹦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爷辙,我是刑警寧澤彬坏,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站膝晾,受9級(jí)特大地震影響栓始,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玷犹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一混滔、第九天 我趴在偏房一處隱蔽的房頂上張望洒疚。 院中可真熱鬧歹颓,春花似錦、人聲如沸油湖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乏德。三九已至撤奸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喊括,已是汗流浹背胧瓜。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郑什,地道東北人府喳。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蘑拯,于是被迫代替她去往敵國(guó)和親钝满。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348