LinkedList源碼剖析(看不懂直播寫檢討)

將分析以下內(nèi)容

  • 字段
  • 構(gòu)造函數(shù)
  • 研究插入和刪除
  • 研究查詢

1.首先來看一下LinkedList里面的屬性

這個(gè)是數(shù)組存儲元素的總數(shù),相信size()方法大家都用過

transient int size = 0;

下面兩個(gè)分別是首節(jié)點(diǎn)和為節(jié)點(diǎn)

transient Node<E> first;
transient Node<E> last;

這里順便介紹一下數(shù)組中存儲的基本單位——節(jié)點(diǎn)(包括節(jié)點(diǎn)內(nèi)容,上一節(jié)點(diǎn),下一節(jié)點(diǎn))

private static class Node<E> {
    E item; //節(jié)點(diǎn)存儲的內(nèi)容
    Node<E> next; //下一節(jié)點(diǎn)
    Node<E> prev; //上一節(jié)點(diǎn)
    //三個(gè)參數(shù)的構(gòu)造函數(shù)
    Node(Node<E> prev, E element, Node<E> next) {...}
}

1.接下來看一下LinkedList里面的構(gòu)造函數(shù)

如果是無參的構(gòu)造函數(shù),僅僅只會(huì)進(jìn)行初始化

public LinkedList() {}

在介紹下一個(gè)構(gòu)造函數(shù)之前,我們先來看一看LinkedList是如何獲取元素的

public E get(int index) {
   checkElementIndex(index); //判斷角標(biāo)是否越界
    return node(index).item;
}

這里我們可以看到需要調(diào)用一個(gè)node()方法,并且我們可以知道該方法返回的是一個(gè)Node對象,接下來我們就看一看該方法

Node<E> node(int index) {                       //index=34; size=100
    //判斷查詢索引在數(shù)組中點(diǎn)左邊還是右邊
    //左邊:從第一個(gè)節(jié)點(diǎn)開始遍歷
    //右邊:從最后一個(gè)節(jié)點(diǎn)開始遍歷
    if (index < (size >> 1)) {                  //index(34) < (size >> 1)(50)
        Node<E> x = first;                      //從首節(jié)點(diǎn)開始遍歷,找到索引節(jié)點(diǎn)并返回
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

可以看到node()方法需要從首節(jié)點(diǎn)或是尾節(jié)點(diǎn)一個(gè)一個(gè)遍歷,所以說當(dāng)需要查詢普遍時(shí)不要用LinkedList
接下來讓我們來看一看LinkedList的另一構(gòu)造函數(shù)

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

該構(gòu)造函數(shù)傳遞了一個(gè)集合c,需要調(diào)用addAll()方法

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

addAll又需要調(diào)用addAll的另一個(gè)重載函數(shù),注意此時(shí)將size也就是當(dāng)前數(shù)組存儲元素的總數(shù)作為下面函數(shù)的index索引

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index); //檢查索引位置是否可做插入,也就比checkElementIndex多了size
    Object[] a = c.toArray();
    int numNew = a.length;
    //長度為空的話什么也不做
    if (numNew == 0)
        return false;
    //記錄插入的前置節(jié)點(diǎn)和后置節(jié)點(diǎn)
    Node<E> pred, succ;
    //如果未指定角標(biāo),將succ設(shè)置為空,將pred設(shè)置為最后一個(gè)節(jié)點(diǎn)
    //否則將succ設(shè)置為指定索引處的節(jié)點(diǎn),pred設(shè)置為succ的上一節(jié)點(diǎn)
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }
    //循環(huán)設(shè)置每個(gè)節(jié)點(diǎn)的前置和后置節(jié)點(diǎn)
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        //創(chuàng)建新節(jié)點(diǎn),設(shè)置節(jié)點(diǎn)內(nèi)容和前置節(jié)點(diǎn)
        Node<E> newNode = new Node<>(pred, e, null);
        //如果上一節(jié)點(diǎn)為空,說明為首節(jié)點(diǎn),并設(shè)置first
        //否則設(shè)置前置節(jié)點(diǎn)的后置節(jié)點(diǎn),使兩個(gè)節(jié)點(diǎn)相互關(guān)聯(lián)
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }
    //succ為空,說明未指定索引,設(shè)置尾節(jié)點(diǎn)
    //否則關(guān)聯(lián)succ和所插入的最后一個(gè)節(jié)點(diǎn)
    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }
    
    size += numNew; //修改當(dāng)前數(shù)組存儲元素的總數(shù)
    modCount++; //數(shù)組修改的次數(shù),該變量在AbstractList定義,不研究
    return true;
}

為了方便理解,下面畫了一個(gè)圖,這是從中間插入集合,注意當(dāng)使用集合初始化LinkedList是直接從末尾差入,這里相當(dāng)于調(diào)用了addAll(int index, Collection<? extends E> c)方法,從指定位置插入集合


在這里插入圖片描述

理解了addAll()再來看add()就非常簡單了

public boolean add(E e) {
    linkLast(e);
    return true;
}
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++; //數(shù)組修改的次數(shù),該變量在AbstractList定義,不研究
}

這里不做細(xì)說了,和上面一樣,無非就是創(chuàng)建一個(gè)節(jié)點(diǎn),維護(hù)該節(jié)點(diǎn)和前置節(jié)點(diǎn)的關(guān)系
最后我們來看一下LinkedList是如何實(shí)現(xiàn)刪除

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

這里可以看到刪除的具體邏輯是封裝在unlink里面的

E unlink(Node<E> x) {
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //刪除節(jié)點(diǎn)為首節(jié)點(diǎn),設(shè)置首節(jié)點(diǎn)為后置節(jié)點(diǎn)
        //否則設(shè)置前置節(jié)點(diǎn)的后置節(jié)點(diǎn)為刪除節(jié)點(diǎn)的后置節(jié)點(diǎn)
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        //刪除節(jié)點(diǎn)為尾節(jié)點(diǎn),設(shè)置后置節(jié)點(diǎn)為前置節(jié)點(diǎn)
        //否則設(shè)置后置節(jié)點(diǎn)的前置節(jié)點(diǎn)為刪除節(jié)點(diǎn)的前置節(jié)點(diǎn)
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
    
        x.item = null;
        size--;
        modCount++;
        return element;
    }
在這里插入圖片描述

下面是另一個(gè)刪除方法,這里不過細(xì)說

public boolean remove(Object o) {
    //循環(huán)遍歷
    //當(dāng)傳入對象為null,==判斷
    //否則equals判斷
    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;
}

總結(jié)——

通過以上我們明白了LinkedList為什么不適合做查詢

  • get()方法會(huì)循環(huán)不斷調(diào)用node.next或node.prev以找到查詢元素,它沒有數(shù)組的索引,只能通過節(jié)點(diǎn)之間的關(guān)聯(lián)進(jìn)行查詢

為什么適合做插入和刪除

  • 只需要修改節(jié)點(diǎn)之間的關(guān)聯(lián)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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