Java源碼解析(三): 從源碼角度徹底搞懂LinkedList

*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

一紧憾、概述

LinkedList稿辙,相對(duì)于ArrayList嘱巾,大家可能平時(shí)使用LinkedList要少一些,其實(shí)有時(shí)候使用LinkedList比ArrayList效率高很多物咳,當(dāng)然锣险,這得視情況而定。

本文將帶大家深入LinkedList源碼览闰,分析其背后的實(shí)現(xiàn)原理芯肤,以便以后在合適的情況下進(jìn)行使用。

之前我所知道的LinkedList的知識(shí):

  • LinkedList底層是鏈表結(jié)構(gòu)
  • 插入和刪除比較快(O(1))压鉴,查詢則相對(duì)慢一些(O(n))
  • 因?yàn)槭擎湵斫Y(jié)構(gòu)崖咨,所以分配的空間不要求是連續(xù)的

二、鏈表

因?yàn)長inkedList源碼中很多地方是進(jìn)行鏈表操作,所以先帶大家復(fù)習(xí)一下鏈表的基礎(chǔ)知識(shí).以前用C語言實(shí)現(xiàn)的鏈表,大家可以去看一下,地址:https://github.com/xfhy/dataStructure

1. 單鏈表

image

一個(gè)節(jié)點(diǎn)中包含數(shù)據(jù)和下一個(gè)節(jié)點(diǎn)的指針(注意,是下一個(gè)節(jié)點(diǎn)的指針,而不是下一個(gè)節(jié)點(diǎn)數(shù)據(jù)的指針),尾節(jié)點(diǎn)沒有下一個(gè)節(jié)點(diǎn),所以指向null.訪問某個(gè)節(jié)點(diǎn)只能從頭節(jié)點(diǎn)開始查找,然后依次往后遍歷.

2. 單向循環(huán)鏈表

image

單向循環(huán)鏈表比單鏈表多了一個(gè)尾節(jié)點(diǎn)的指針指向的是頭結(jié)點(diǎn).

3. 雙向鏈表

image

雙向鏈表的每個(gè)節(jié)點(diǎn)包含以下數(shù)據(jù):上一個(gè)節(jié)點(diǎn)的指針,自己的數(shù)據(jù),下一個(gè)節(jié)點(diǎn)的指針.尾節(jié)點(diǎn)沒有下一個(gè)節(jié)點(diǎn),所以指向null.這樣的結(jié)構(gòu),比如我拿到鏈表中間的一個(gè)節(jié)點(diǎn),即可以往前遍歷,也可以往后遍歷.

4. 雙向循環(huán)鏈表

image

雙向循環(huán)鏈表的尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是頭結(jié)點(diǎn),頭節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)是尾節(jié)點(diǎn).

三晴弃、LinkedList的繼承關(guān)系

image

源碼中的定義:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • AbstractSequentialList這個(gè)類提供了List的一個(gè)骨架實(shí)現(xiàn)接口,以盡量減少實(shí)現(xiàn)此接口所需的工作量由“順序訪問”數(shù)據(jù)存儲(chǔ)(如鏈接列表)支持。對(duì)于隨機(jī)訪問數(shù)據(jù)(如數(shù)組)上鞠,應(yīng)使用AbstractList優(yōu)先于此類际邻。

  • 實(shí)現(xiàn)了List接口,意味著LinkedList元素是有序的,可以重復(fù)的,可以有null元素的集合.

  • Deque是Queue的子接口,Queue是一種隊(duì)列形式,而Deque是雙向隊(duì)列,它支持從兩個(gè)端點(diǎn)方向檢索和插入元素.

  • 實(shí)現(xiàn)了Cloneable接口,標(biāo)識(shí)著可以它可以被復(fù)制.注意,ArrayList里面的clone()復(fù)制其實(shí)是淺復(fù)制(不知道此概念的趕快去查資料,這知識(shí)點(diǎn)非常重要).

  • 實(shí)現(xiàn)了Serializable 標(biāo)識(shí)著集合可被序列化。

四芍阎、看LinkedList源碼前的準(zhǔn)備

1. 節(jié)點(diǎn)定義

private static class Node<E> {
    E item;  //該節(jié)點(diǎn)的數(shù)據(jù)
    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;
    }
}

Node是LinkedList的靜態(tài)內(nèi)部類.

為什么是靜態(tài)內(nèi)部類?我覺得可能原因如下:普通內(nèi)部類會(huì)有外部類的強(qiáng)引用,而靜態(tài)內(nèi)部類就沒有.有外部類的強(qiáng)引用的話,很容易造成內(nèi)存泄漏,寫成靜態(tài)內(nèi)部類可以避免這種情況的發(fā)生.

2. 成員變量

看構(gòu)造方法之前先看看幾個(gè)屬性:

//鏈表長度
transient int size = 0;
/**
* 頭結(jié)點(diǎn)
*/
transient Node<E> first;

/**
* 尾節(jié)點(diǎn)
*/
transient Node<E> last;

這里為什么要存在一個(gè)成員變量尾節(jié)點(diǎn)?我感覺是為了方便,比如查找相應(yīng)索引處元素+插入元素到最后.查找相應(yīng)索引處元素時(shí),先判斷索引是在前半段還是在后半段,如果是在后半段,那么直接從尾節(jié)點(diǎn)出發(fā),從后往前進(jìn)行查找,這樣速度更快.在插入元素到最后時(shí),可以直接通過尾節(jié)點(diǎn)方便的進(jìn)行插入.

3. 構(gòu)造方法

下面是構(gòu)造方法源碼:

/**
* 構(gòu)造一個(gè)空列表
*/
public LinkedList() {
}

/**
* 構(gòu)造列表通過指定的集合
*/
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

兩個(gè)構(gòu)造方法都比較簡單,就是構(gòu)造一個(gè)列表,其中的addAll()方法待會(huì)兒放到后面分析.

思考:為什么LinkedList沒有提供public LinkedList(int initialCapacity)這種構(gòu)建指定大小列表的構(gòu)造方式?

因?yàn)锳rrayList有這種構(gòu)造方法public ArrayList(int initialCapacity),ArrayList提供這種構(gòu)造方法的好處在于在知道需要多大的空間的情況下,可以按需構(gòu)造列表,無需浪費(fèi)多余的空間和不必要的生成新數(shù)組的操作.而LinkedList可以很輕松動(dòng)態(tài)的增加元素(O(1)),所以沒必要一開始就構(gòu)造一個(gè)有很多元素的列表,到時(shí)需要的時(shí)候再按需加上去就行了.

五世曾、添加元素

1. add(E e)

方法作用:將e添加到鏈表末尾,返回是否添加成功

/**
* 添加指定元素到鏈表尾部
*/
public boolean add(E e) {
    linkLast(e);
    return true;
}
/**
* Links e as last element.將e添加到尾部
*/
void linkLast(E e) {
    //1. 暫記尾節(jié)點(diǎn)
    final Node<E> l = last;
    //2. 構(gòu)建節(jié)點(diǎn) 前一個(gè)節(jié)點(diǎn)是之前的尾節(jié)點(diǎn)
    final Node<E> newNode = new Node<>(l, e, null);
    //3. 新建的節(jié)點(diǎn)是尾節(jié)點(diǎn)了
    last = newNode;
    //4. 判斷之前鏈表是否為空  
    //為空則將新節(jié)點(diǎn)賦給頭結(jié)點(diǎn)(相當(dāng)于空鏈表插入第一個(gè)元素,頭結(jié)點(diǎn)等于尾節(jié)點(diǎn))
    //非空則將之前的尾節(jié)點(diǎn)指向新節(jié)點(diǎn)
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    //5. 鏈表長度增加
    size++;
    modCount++;
}

大體思路:

  1. 構(gòu)建一個(gè)新的節(jié)點(diǎn)
  2. 將該新節(jié)點(diǎn)作為新的尾節(jié)點(diǎn).如果是空鏈表插入第一個(gè)元素,那么頭結(jié)點(diǎn)=尾節(jié)點(diǎn)=新節(jié)點(diǎn);如果不是,那么將之前的尾節(jié)點(diǎn)指向新節(jié)點(diǎn).
  3. 增加鏈表長度

小細(xì)節(jié)
boolean add(E e)添加成功返回true,添加失敗返回false.我們?cè)诖a中沒有看到有返回false的情況啊,直接在代碼中寫了個(gè)返回true,什么判斷條件都沒有,啊??

image

仔細(xì)想想,分配內(nèi)存空間不是必須是連續(xù)的,所以只要是還能給它分配空間,就不會(huì)添加失敗.當(dāng)空間不夠分配時(shí)(內(nèi)存溢出),會(huì)拋出OutOfMemory.

2. addLast(E e)

方法作用:添加元素到末尾. 內(nèi)部實(shí)現(xiàn)和add(E e)一樣.

public void addLast(E e) {
    linkLast(e);
}

3. addFirst(E e)

方法作用:添加元素到鏈表頭部

public void addFirst(E e) {
    linkFirst(e);
}
/**
* 添加元素到鏈表頭部
*/
private void linkFirst(E e) {
    //1. 記錄頭結(jié)點(diǎn)
    final Node<E> f = first;
    //2. 創(chuàng)建新節(jié)點(diǎn)  next指針指向之前的頭結(jié)點(diǎn)
    final Node<E> newNode = new Node<>(null, e, f);
    //3. 新建的節(jié)點(diǎn)就是頭節(jié)點(diǎn)了
    first = newNode;
    //4. 判斷之前鏈表是否為空  
    //為空則將新節(jié)點(diǎn)賦給尾節(jié)點(diǎn)(相當(dāng)于空鏈表插入第一個(gè)元素,頭結(jié)點(diǎn)等于尾節(jié)點(diǎn))
    //非空則將之前的頭結(jié)點(diǎn)的prev指針指向新節(jié)點(diǎn)
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    //5. 鏈表長度增加
    size++;
    modCount++;
}

大體思路:

  1. 構(gòu)建一個(gè)新的節(jié)點(diǎn)
  2. 將該新節(jié)點(diǎn)作為新的頭節(jié)點(diǎn).如果是空鏈表插入第一個(gè)元素,那么頭結(jié)點(diǎn)=尾節(jié)點(diǎn)=新節(jié)點(diǎn);如果不是,那么將之前的頭節(jié)點(diǎn)的prev指針指向新節(jié)點(diǎn).
  3. 增加鏈表長度

4. push(E e)

方法作用:添加元素到鏈表頭部 這里的意思比擬壓棧.和pop(出棧:移除鏈表第一個(gè)元素)相反.

內(nèi)部實(shí)現(xiàn)是和addFirst()一樣的.

public void push(E e) {
    addFirst(e);
}

5. offer(),offerFirst(E e),offerLast(E e)

方法作用:添加元素到鏈表頭部. 內(nèi)部實(shí)現(xiàn)其實(shí)就是add(e)

public boolean offer(E e) {
    return add(e);
}
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

/**
* 添加元素到末尾
*/
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

6. add(int index, E element)

方法作用:添加元素到指定位置,可能會(huì)拋出IndexOutOfBoundsException

//添加元素到指定位置
public void add(int index, E element) {
    //1. 越界檢查
    checkPositionIndex(index);

    //2. 判斷一下index大小
    //如果是和list大小一樣,那么就插入到最后
    //否則插入到index處
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

//檢查是否越界
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
* Returns the (non-null) Node at the specified element index.
返回指定元素索引處的(非空)節(jié)點(diǎn)。
*/
Node<E> node(int index) {
    // assert isElementIndex(index);

    /**
    * 這里的思想非常巧妙,如果index在鏈表的前半部分,那么從first開始往后查找
    否則,從last往前面查找
    */
    //1. 如果index<size/2 ,即index在鏈表的前半部分
    if (index < (size >> 1)) {
        //2. 記錄下第一個(gè)節(jié)點(diǎn)
        Node<E> x = first;
        //3. 循環(huán)從第一個(gè)節(jié)點(diǎn)開始往后查,直到到達(dá)index處,返回index處的元素
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //index在鏈表的后半部分
        //4. 記錄下最后一個(gè)節(jié)點(diǎn)
        Node<E> x = last;
        //5. 循環(huán)從最后一個(gè)節(jié)點(diǎn)開始往前查,直到到達(dá)index處,返回index處的元素
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
/**
* Links e as last element.
將e鏈接到list最后一個(gè)元素
*/
void linkLast(E e) {
    //1. 記錄最后一個(gè)元素l
    final Node<E> l = last;
    //2. 構(gòu)建一個(gè)新節(jié)點(diǎn),數(shù)據(jù)為e,前一個(gè)是l,后一個(gè)是null
    final Node<E> newNode = new Node<>(l, e, null);
    //3. 現(xiàn)在新節(jié)點(diǎn)是最后一個(gè)元素了,所以需要記錄下來
    last = newNode;
    //4. 如果之前l(fā)ist為空,那么first=last=newNode,只有一個(gè)元素
    if (l == null)
        first = newNode;
    else
        //5. 非空的話,那么將之前的最后一個(gè)指向新的節(jié)點(diǎn)
        l.next = newNode;
    //6. 鏈表長度+1
    size++;
    modCount++;
}

/**
* Inserts element e before non-null Node succ.
在非null節(jié)點(diǎn)succ之前插入元素e谴咸。
*/
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //1. 記錄succ的前一個(gè)節(jié)點(diǎn)
    final Node<E> pred = succ.prev;
    //2. 構(gòu)建一個(gè)新節(jié)點(diǎn),數(shù)據(jù)是e,前一個(gè)節(jié)點(diǎn)是pred,下一個(gè)節(jié)點(diǎn)是succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    //3. 將新節(jié)點(diǎn)作為succ的前一個(gè)節(jié)點(diǎn)
    succ.prev = newNode;
    //4. 判斷pred是否為空
    //如果為空,那么說明succ是之前的頭節(jié)點(diǎn),現(xiàn)在新節(jié)點(diǎn)在succ的前面,所以新節(jié)點(diǎn)是頭節(jié)點(diǎn)
    if (pred == null)
        first = newNode;
    else
        //5. succ的前一個(gè)節(jié)點(diǎn)不是空的話,那么直接將succ的前一個(gè)節(jié)點(diǎn)指向新節(jié)點(diǎn)就可以了
        pred.next = newNode;
    //6. 鏈表長度+1
    size++;
    modCount++;
}

大體思路:

  1. 首先判斷一下插入的位置是在鏈表的最后還是在鏈表中間.
  2. 如果是插入到鏈表末尾,那么將之前的尾節(jié)點(diǎn)指向新節(jié)點(diǎn)
  3. 如果是插入到鏈表中間
    1. 需要先找到鏈表中index索引處的節(jié)點(diǎn).
    2. 將新節(jié)點(diǎn)賦值為index處節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)
    3. 將index處節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的next指針賦值為新節(jié)點(diǎn)

哇,這里描述起來有點(diǎn)困難,,,,不知道我描述清楚沒有.如果沒看懂我的描述,看一下代碼+再結(jié)合代碼注釋+畫一下草圖應(yīng)該更清晰一些.

6. addAll(int index, Collection<? extends E> c)

方法作用:將指定集合的所有元素插入到index位置

//將指定集合的所有元素插入到末尾位置
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

//將指定集合的所有元素插入到index位置
public boolean addAll(int index, Collection<? extends E> c) {
    //1. 入?yún)⒑戏ㄐ詸z查
    checkPositionIndex(index);

    //2. 將集合轉(zhuǎn)成數(shù)組
    Object[] a = c.toArray();
    //3. 記錄需要插入的集合元素個(gè)數(shù)
    int numNew = a.length;
    //4. 如果個(gè)數(shù)為0,那么插入失敗,不繼續(xù)執(zhí)行了
    if (numNew == 0)
        return false;

    //5. 判斷一下index與size是否相等
    //相等則插入到鏈表末尾
    //不相等則插入到鏈表中間  index處   
    Node<E> pred, succ;   
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        //找到index索引處節(jié)點(diǎn)  這樣就可以方便的拿到該節(jié)點(diǎn)的前后節(jié)點(diǎn)信息
        succ = node(index);
        //記錄index索引處節(jié)點(diǎn)前一個(gè)節(jié)點(diǎn)
        pred = succ.prev;
    }

    //6. 循環(huán)將集合中所有元素連接到pred后面
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        //如果前一個(gè)是空,那么將新節(jié)點(diǎn)作為頭結(jié)點(diǎn)
        if (pred == null)
            first = newNode;
        else
            //指向新節(jié)點(diǎn)
            pred.next = newNode;
        pred = newNode;
    }

    //7. 判斷succ是否為空
    //為空的話,那么集合的最后一個(gè)元素就是尾節(jié)點(diǎn)
    //非空的話,那么將succ連接到集合的最后一個(gè)元素后面
    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    //8. 鏈表長度+numNew
    size += numNew;
    modCount++;
    return true;
}

大體思路:

  1. 將需要添加的集合轉(zhuǎn)成數(shù)組a
  2. 判斷需要插入的位置index是否等于鏈表長度size,如果相等則插入到鏈表最后;如果不相等,則插入到鏈表中間,還需要找到index處節(jié)點(diǎn)succ,方便拿到該節(jié)點(diǎn)的前后節(jié)點(diǎn)信息.
  3. 記錄index索引處節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)pred,循環(huán)將集合中所有元素連接到pred的后面
  4. 將集合最后一個(gè)元素的next指針指向succ,將succ的prev指針指向集合的最后一個(gè)元素

六轮听、刪除元素

1. remove(),removeFirst()

方法作用: 移除鏈表第一個(gè)元素

/**
* 移除鏈表第一個(gè)節(jié)點(diǎn)
*/
public E remove() {
    return removeFirst();
}

/**
* 移除鏈表第一個(gè)節(jié)點(diǎn)
*/
public E removeFirst() {
    final Node<E> f = first;
    //注意:如果之前是空鏈表,移除是要報(bào)錯(cuò)的喲
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

/**
* Unlinks non-null first node f.
* 將第一個(gè)節(jié)點(diǎn)刪掉
*/
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    //1. 記錄第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)值
    final E element = f.item;
    //2. 記錄下一個(gè)節(jié)點(diǎn)
    final Node<E> next = f.next;
    //3. 將第一個(gè)節(jié)點(diǎn)置空  幫助GC回收
    f.item = null;
    f.next = null; // help GC
    //4. 記錄頭節(jié)點(diǎn)
    first = next;
    //5. 如果下一個(gè)節(jié)點(diǎn)為空,那么鏈表無節(jié)點(diǎn)了    如果不為空,將頭節(jié)點(diǎn)的prev指針置為空
    if (next == null)
        last = null;
    else
        next.prev = null;
    //6. 鏈表長度-1
    size--;
    modCount++;
    //7. 返回刪除的節(jié)點(diǎn)的數(shù)據(jù)值
    return element;
}

大體思路:其實(shí)就是將第一個(gè)節(jié)點(diǎn)移除并置空,然后將第二個(gè)節(jié)點(diǎn)作為頭節(jié)點(diǎn).思路還是非常清晰的,主要是對(duì)細(xì)節(jié)的處理.

2. remove(int index)

方法作用:移除指定位置元素

//移除指定位置元素
public E remove(int index) {
    //檢查入?yún)⑹欠窈戏?    checkElementIndex(index);
    //node(index)找到index處的節(jié)點(diǎn)  
    return unlink(node(index));
}

//移除節(jié)點(diǎn)x
E unlink(Node<E> x) {
    // assert x != null;
    //1. 記錄該節(jié)點(diǎn)數(shù)據(jù)值,前一個(gè)節(jié)點(diǎn)prev,后一個(gè)節(jié)點(diǎn)next
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    //2. 判斷前一個(gè)節(jié)點(diǎn)是否為空
    if (prev == null) {
        //為空的話,那么說明之前x節(jié)點(diǎn)是頭節(jié)點(diǎn)  這時(shí)x的下一個(gè)節(jié)點(diǎn)成為頭節(jié)點(diǎn)
        first = next;
    } else {
        //非空的話,將前一個(gè)節(jié)點(diǎn)的next指針指向x的下一個(gè)節(jié)點(diǎn)
        prev.next = next;
        //x的prev置為null
        x.prev = null;
    }

    //3. 判斷x后一個(gè)節(jié)點(diǎn)是否為空
    if (next == null) {
        //為空的話,那么說明之前x節(jié)點(diǎn)是尾節(jié)點(diǎn),這時(shí)x的前一個(gè)節(jié)點(diǎn)成為尾節(jié)點(diǎn)
        last = prev;
    } else {
        //為空的話,將x的下一個(gè)節(jié)點(diǎn)的prev指針指向prev(x的前一個(gè)節(jié)點(diǎn))
        next.prev = prev;
        //x的next指針置空
        x.next = null;
    }

    //4. x節(jié)點(diǎn)數(shù)據(jù)值置空
    x.item = null;
    //5. 鏈表長度-1
    size--;
    modCount++;
    //6. 將x節(jié)點(diǎn)的數(shù)據(jù)值返回
    return element;
}

大體思路:

  1. 首先找到index索引處的節(jié)點(diǎn)(這樣就可以方便的獲取該節(jié)點(diǎn)的前后節(jié)點(diǎn)),記為x
  2. 記錄x的前(prev)后(next)節(jié)點(diǎn)
  3. 將x的前一個(gè)節(jié)點(diǎn)prev節(jié)點(diǎn)的next指針指向next,將x節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn)的prev指針指向prev節(jié)點(diǎn).
  4. 將x節(jié)點(diǎn)置空,鏈表長度-1

3. remove(Object o)

方法作用:從此鏈表中刪除第一次出現(xiàn)的指定元素o

public boolean remove(Object o) {
    //1. 判斷o是否為空
    if (o == null) {
        //為null  循環(huán),找第一個(gè)數(shù)據(jù)值為null的節(jié)點(diǎn)
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                //刪除該節(jié)點(diǎn)
                unlink(x);
                return true;
            }
        }
    } else {
        //非空  循環(huán),找第一個(gè)與o的數(shù)據(jù)值相等的節(jié)點(diǎn)
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                //刪除該節(jié)點(diǎn)
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

大體思路:

  1. 首先判斷入?yún)⑹欠駷閚ull
  2. 如果為null,那么循環(huán)遍歷鏈表,從頭節(jié)點(diǎn)開始往后查找,找到第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)值為null的,直接刪除該節(jié)點(diǎn).
  3. 如果非null,那么循環(huán)遍歷鏈表,從頭節(jié)點(diǎn)開始往后查找,找到第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)值為o的,直接刪除該節(jié)點(diǎn).

這里的循環(huán)遍歷鏈表的代碼,我覺得還是比較通用的,從頭節(jié)點(diǎn)開始,通過不斷的將x賦值為下一個(gè)元素,直到遍歷到為null的地方結(jié)束,這樣就完美的遍歷完了鏈表所有節(jié)點(diǎn).

4. removeFirstOccurrence(Object o)

方法作用:從此鏈表中刪除第一次出現(xiàn)的指定元素o. 內(nèi)部其實(shí)就是上面的remove(o);

public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

5. removeLast()

方法作用:移除最后一個(gè)元素并返回

public E removeLast() {
    final Node<E> l = last;
    //如果鏈表是空的,那么就要拋出一個(gè)錯(cuò)誤
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}
/**
* Unlinks non-null last node l.
移除鏈表最后一個(gè)元素
*/
private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;

    //1. 記錄尾節(jié)點(diǎn)數(shù)據(jù)值
    final E element = l.item;
    //2. 找到尾節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)prev
    final Node<E> prev = l.prev;
    //3. 將尾節(jié)點(diǎn)置空  方便GC
    l.item = null;
    l.prev = null; // help GC
    //4. 將last賦值為prev  
    last = prev;
    //5. 判斷prev是否為null
    //為空的話,說明之前鏈表就只有1個(gè)節(jié)點(diǎn),現(xiàn)在刪了之后,頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都為null了
    //非空,直接將新任尾節(jié)點(diǎn)的next指針指向null
    if (prev == null)
        first = null;
    else
        prev.next = null;
    //6. 鏈表長度-1
    size--;
    modCount++;
    //7. 返回之前尾節(jié)點(diǎn)數(shù)據(jù)值
    return element;
}

大體思路:

  1. 判斷鏈表是否有節(jié)點(diǎn), 沒有節(jié)點(diǎn)直接拋錯(cuò)誤....
  2. 首先找到倒數(shù)第二個(gè)節(jié)點(diǎn)(可能沒有哈,沒有的話,說明鏈表只有一個(gè)節(jié)點(diǎn))prev
  3. 然后將尾節(jié)點(diǎn)置空,prev的next指針指向null

6. removeLastOccurrence(Object o)

方法作用:從此鏈表中刪除最后一次出現(xiàn)的指定元素o.

實(shí)現(xiàn):其實(shí)和上面的remove(o)是一樣的,只不過這里遍歷時(shí)是從尾節(jié)點(diǎn)開始往前查找的.

public boolean removeLastOccurrence(Object o) {
    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;
}

7. poll()

方法作用:獲取第一個(gè)元素的同時(shí)刪除第一個(gè)元素,當(dāng)鏈表無節(jié)點(diǎn)時(shí),不會(huì)報(bào)錯(cuò). 這里的unlinkFirst()上面已分析過.

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

8. pop()

方法作用:獲取第一個(gè)元素的同時(shí)刪除第一個(gè)元素,當(dāng)鏈表無節(jié)點(diǎn)時(shí),會(huì)報(bào)錯(cuò).

public E pop() {
    return removeFirst();
}
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

七、修改元素

1. set(int index, E element)

方法作用:設(shè)置index處節(jié)點(diǎn)數(shù)據(jù)值為element

public E set(int index, E element) {
    //1. 入?yún)z測(cè)
    checkElementIndex(index);
    //2. 找到index處節(jié)點(diǎn),上面已分析該方法
    Node<E> x = node(index);
    //3. 保存該節(jié)點(diǎn)舊值
    E oldVal = x.item;
    //4. 替換為新值
    x.item = element;
    //5. 將舊值返回
    return oldVal;
}

大體思路:非常簡單,就是首先找到index處節(jié)點(diǎn),替換該節(jié)點(diǎn)數(shù)據(jù)值

八岭佳、查詢?cè)?/h2>

1. element()

方法作用:獲取鏈表第一個(gè)元素. 方法比較簡單,就是將鏈表頭節(jié)點(diǎn)數(shù)據(jù)值進(jìn)行返回

public E element() {
    return getFirst();
}
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

2. get(int index)

方法作用:獲取指定索引處元素. 方法比較簡單,就是通過node(index)找到index索引處節(jié)點(diǎn),然后返回其數(shù)據(jù)值

public E get(int index) {
    //1. 入?yún)z測(cè)
    checkElementIndex(index);
    //2. 獲取指定索引處節(jié)點(diǎn)數(shù)據(jù)值
    return node(index).item;
}

3. getFirst()

方法作用:獲取鏈表第一個(gè)元素. 非常簡單,就是將first的數(shù)據(jù)值返回

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

4. getLast()

方法作用:獲取鏈表最后一個(gè)元素. 非常簡單,就是將last的數(shù)據(jù)值返回

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

5. 通過listIterator()遍歷

這也是查詢的一種,哈哈

我們先來看看listIterator(int index)方法,就是new了一個(gè)ListItr進(jìn)行返回.ListItr是LinkedList的內(nèi)部類.

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

接下來,我們看看這個(gè)內(nèi)部類:

private class ListItr implements ListIterator<E> {
    //上一次返回的節(jié)點(diǎn)
    private Node<E> lastReturned;
    //下一個(gè)節(jié)點(diǎn)
    private Node<E> next;
    //下一個(gè)節(jié)點(diǎn)索引
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        //如果是最后一個(gè)節(jié)點(diǎn),那么返回next是null    
        //如果不是最后一個(gè)節(jié)點(diǎn),那么找到該index索引處節(jié)點(diǎn)
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        //判斷是否還有下一個(gè)元素
        return nextIndex < size;
    }

    //獲取下一個(gè)元素
    public E next() {
        checkForComodification();
        //1. 如果沒有下一個(gè)元素   拋異常
        if (!hasNext())
            throw new NoSuchElementException();

        //2. 記錄上一次遍歷到的節(jié)點(diǎn)
        lastReturned = next;
        //3. 往后移
        next = next.next;
        //4. 索引+1
        nextIndex++;
        //5. 將遍歷到的節(jié)點(diǎn)數(shù)據(jù)值返回
        return lastReturned.item;
    }

    public boolean hasPrevious() {
        //判斷是否還有前一個(gè)元素
        return nextIndex > 0;
    }

    //獲取前一個(gè)元素
    public E previous() {
        checkForComodification();
        //1. 如果沒有前一個(gè)元素,則拋異常
        if (!hasPrevious())
            throw new NoSuchElementException();

        //2. 當(dāng)next是null的時(shí)候,賦值為last     
        //不是null的時(shí)候,往前移動(dòng)
        lastReturned = next = (next == null) ? last : next.prev;
        //3. index-1  因?yàn)槭峭?        nextIndex--;
        //4. 將遍歷到的節(jié)點(diǎn)數(shù)據(jù)值返回
        return lastReturned.item;
    }

    public int nextIndex() {
        return nextIndex;
    }

    public int previousIndex() {
        return nextIndex - 1;
    }

    //移除當(dāng)前遍歷到的元素
    public void remove() {
        checkForComodification();
        //1. 移除當(dāng)前遍歷到的元素為null,直接拋錯(cuò)誤
        if (lastReturned == null)
            throw new IllegalStateException();

        //2. 記錄當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
        Node<E> lastNext = lastReturned.next;
        //3. 刪除當(dāng)前節(jié)點(diǎn)
        unlink(lastReturned);
        //4. 如果next == lastReturned,說明當(dāng)前是從前往后遍歷的,那么將next賦值為下一個(gè)節(jié)點(diǎn)
        //如果不相等,那么說明是從后往前遍歷的,這時(shí)只需要將index-1就行了
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        //5. 將移除的節(jié)點(diǎn)置空
        lastReturned = null;
        expectedModCount++;
    }

    //設(shè)置當(dāng)前正在遍歷的節(jié)點(diǎn)的值   啥?用ListIterator居然可以在遍歷的時(shí)候修改值,,666
    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        //設(shè)置當(dāng)前遍歷的節(jié)點(diǎn)的值
        lastReturned.item = e;
    }

    //添加一個(gè)值
    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        //如果next為null,那么添加到最后
        //否則,將e元素添加到next的前面
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }

    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        //循環(huán) 往后遍歷   沒遍歷一個(gè)節(jié)點(diǎn)就回調(diào)當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)值
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }

    //判斷一下該列表是否被其他線程改過(在迭代過程中)   修改過則拋異常
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

這里的ListIterator有點(diǎn)強(qiáng)

  • ListIterator只能用于List及其子類型血巍。
  • 有add()方法,可以往鏈表中添加對(duì)象
  • 可以通過hasNext()和next()往后順序遍歷,也可以通過hasPrevious()和previous()實(shí)現(xiàn)往前遍歷
  • 可以通過nextIndex()和previousIndex()返回當(dāng)前索引處的位置
  • 可以通過set()實(shí)現(xiàn)當(dāng)前遍歷對(duì)象的修改

九、總結(jié)

好了,又到了總結(jié)的時(shí)候,相信各位認(rèn)真看完的應(yīng)該對(duì)鏈表的基本操作非常熟悉了.

下面我們來總結(jié)一下LinkedList的關(guān)鍵點(diǎn)

LinkedList關(guān)鍵點(diǎn)

  • 底層是雙向鏈表存儲(chǔ)數(shù)據(jù),并且記錄了頭節(jié)點(diǎn)和尾節(jié)點(diǎn)
  • 添加元素非成核妫快,如果是添加到頭部和尾部的話更快,因?yàn)橐呀?jīng)記錄了頭節(jié)點(diǎn)和尾節(jié)點(diǎn),只需要鏈接一下就行了. 如果是添加到鏈表的中間部分的話,那么多一步操作,需要先找到添加索引處的元素(因?yàn)樾枰溄拥竭@里),才能進(jìn)行添加.
  • 遍歷的時(shí)候,建議采用forEach()進(jìn)行遍歷,這樣可以在每次獲取下一個(gè)元素時(shí)都非常輕松(next = next.next;). 然后如果是通過foriget(i)的方式進(jìn)行遍歷的話,效率是極低的,每次get(i)都需要從最前面(或者最后面)開始往后查找i索引處的元素,效率很低.
  • 刪除也是非呈龉眩快,只需要改動(dòng)一下指針就行了,代價(jià)很小.

本文有寫的不對(duì)地方,還請(qǐng)多多包涵,歡迎批評(píng)指正.

這是我的筆記的其中一篇,需要看其他筆記的請(qǐng)移步 https://github.com/xfhy/notes叶洞,歡迎star鲫凶、fork.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市衩辟,隨后出現(xiàn)的幾起案子螟炫,更是在濱河造成了極大的恐慌,老刑警劉巖艺晴,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昼钻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡财饥,警方通過查閱死者的電腦和手機(jī)换吧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钥星,“玉大人沾瓦,你說我怎么就攤上這事∏矗” “怎么了贯莺?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宁改。 經(jīng)常有香客問我缕探,道長,這世上最難降的妖魔是什么还蹲? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任爹耗,我火速辦了婚禮耙考,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘潭兽。我一直安慰自己倦始,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布山卦。 她就那樣靜靜地躺著鞋邑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪账蓉。 梳的紋絲不亂的頭發(fā)上枚碗,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音铸本,去河邊找鬼肮雨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛归敬,可吹牛的內(nèi)容都是我干的酷含。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼汪茧,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼椅亚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舱污,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤呀舔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扩灯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體媚赖,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年珠插,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惧磺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捻撑,死狀恐怖磨隘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情顾患,我是刑警寧澤番捂,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站江解,受9級(jí)特大地震影響设预,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜犁河,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一鳖枕、第九天 我趴在偏房一處隱蔽的房頂上張望魄梯。 院中可真熱鬧,春花似錦宾符、人聲如沸画恰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缠局,卻和暖如春则奥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狭园。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工读处, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唱矛。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓罚舱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绎谦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子管闷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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