LinkedList源碼分析.md

概述

LinkedList是一種可以在任何位置進行高效地插入和移除操作的有序序列,它是基於雙向鏈表實現的,是線程不安全的,允許元素爲null的雙向鏈表。

源碼分析

1. 變量

/**
 * 集合元素數量
 **/
transient int size = 0;

/**
 * 指向第一個節點的指針
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * 指向最後一個節點的指針
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

2. 構造方法

/**
 * 無參構造方法
 */
public LinkedList() {
}

/**
 * 將集合c所有元素插入鏈表中
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

3. Node節點

private static class Node<E> {
    // 值
    E item;
    // 後繼
    Node<E> next;
    // 前驅
    Node<E> prev;

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

因爲一個Node既有prev也有next,所以證明它是一個雙向鏈表。

4. 添加元素

addAll(Collection<? extends E> c)

將集合c添加到鏈表,如果不傳index,則默認是添加到尾部。如果調用addAll(int index, Collection<? extends E> c)方法,則添加到index後面。

/**
 * 將集合添加到鏈尾
 */
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

/** 
 * 
 */
public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);

    // 拿到目標集合數組
    Object[] a = c.toArray();
    //新增元素的數量
    int numNew = a.length;
    //如果新增元素數量爲0,則不增加,並返回false
    if (numNew == 0)
        return false;
    
    //定義index節點的前置節點,後置節點
    Node<E> pred, succ;
    // 判斷是否是鏈表尾部,如果是:在鏈表尾部追加數據
    //尾部的後置節點一定是null,前置節點是隊尾
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        // 如果不在鏈表末端(而在中間部位)
        // 取出index節點,並作爲後繼節點
        succ = node(index);
        // index節點的前節點 作爲前驅節點
        pred = succ.prev;
    }

    // 鏈表批量增加,是靠for循環遍歷原數組,依次執行插入節點操作
    for (Object o : a) {
        @SuppressWarnings("unchecked") 
        // 類型轉換
        E e = (E) o;
        // 前置節點爲pred,後置節點爲null,當前節點值爲e的節點newNode
        Node<E> newNode = new Node<>(pred, e, null);
        // 如果前置節點爲空, 則newNode爲頭節點,否則爲pred的next節點
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    // 循環結束後,如果後置節點是null,說明此時是在隊尾追加的
    if (succ == null) {
        // 設置尾節點
        last = pred;
    } else {
    //否則是在隊中插入的節點 ,更新前置節點 後置節點
        pred.next = succ;
        succ.prev = pred;
    }

    // 修改數量size
    size += numNew;
    //修改modCount
    modCount++;
    return true;
}

/**
  * 取出index節點
  */ 
Node<E> node(int index) {
    // assert isElementIndex(index);

    // 如果index 小於 size/2,則從頭部開始找
    if (index < (size >> 1)) {
        // 把頭節點賦值給x
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            // x=x的下一個節點
            x = x.next;
        return x;
    } else {
        // 如果index 大與等於 size/2,則從後面開始找
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}


// 檢測index位置是否合法
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 檢測index位置是否合法
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}    

假設我們要在index=2處添加{1,2}到鏈表中,圖解如下:

第一步:拿到index=2的前驅節點 prev=ele1

第二步:遍歷集合prev.next=newNode,並實時更新prev節點以便下一次
遍歷:prev=newNode

第三步:將index=2的節點ele2接上:prev.next=ele2,ele2.prev=prev

注意node(index)方法:尋找處於index的節點,有一個小優化,結點在前半段則從頭開始遍歷,在後半段則從尾開始遍歷,這樣就保證了只需要遍歷最多一半結點就可以找到指定索引的結點。

addFirst(E e)方法

將e元素添加到鏈表並設置其爲頭節點(first)。

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

//將e鏈接成列表的第一個元素
private void linkFirst(E e) {

    final Node<E> f = first;
    // 前驅爲空,值爲e,後繼爲f
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    //若f爲空,則表明列表中還沒有元素,last也應該指向newNode
    if (f == null)
        last = newNode;
    else
    //否則,前first的前驅指向newNode
        f.prev = newNode;
    size++;
    modCount++;
}
  1. 拿到first節點命名爲f
  2. 新創建一個節點newNode設置其next節點爲f節點
  3. 將newNode賦值給first
  4. 若f爲空,則表明列表中還沒有元素,last也應該指向newNode;否則,前first的前驅指向newNode。
  5. 圖解如下:

addLast(E e)方法

將e元素添加到鏈表並設置其爲尾節點(last)。

public void addLast(E e) {
    linkLast(e);
}
/**
 * 將e鏈接成列表的last元素
 */
void linkLast(E e) {
    final Node<E> l = last;
    // 前驅爲前last,值爲e,後繼爲null
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    //最後一個節點爲空,說明列表中無元素
    if (l == null)
        //first同樣指向此節點
        first = newNode;
    else
        //否則,前last的後繼指向當前節點
        l.next = newNode;
    size++;
    modCount++;
}

過程與linkFirst()方法類似,這裏略過。

add(E e)方法

在尾部追加元素e。

public boolean add(E e) {
    linkLast(e);
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;
    // 前驅爲前last,值爲e,後繼爲null
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    //最後一個節點爲空,說明列表中無元素
    if (l == null)
        //first同樣指向此節點
        first = newNode;
    else
        //否則,前last的後繼指向當前節點
        l.next = newNode;
    size++;
    modCount++;
}
add(int index, E element)方法

在鏈表的index處添加元素element.

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
/**
 * 在succ節點前增加元素e(succ不能爲空)
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // 拿到succ的前驅
    final Node<E> pred = succ.prev;
    // 新new節點:前驅爲pred,值爲e,後繼爲succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 將succ的前驅指向當前節點
    succ.prev = newNode;
    // pred爲空,說明此時succ爲首節點
    if (pred == null)
        // 指向當前節點
        first = newNode;
    else
        // 否則,將succ之前的前驅的後繼指向當前節點
        pred.next = newNode;
    size++;
    modCount++;
}

linkLast方法上文有講。
linkBefore(E e, Node<E> succ)方法步驟:

  1. 拿到succ的前驅節點
  2. 新new節點:前驅爲pred,值爲e,後繼爲succ : Node<>(pred, e, succ);
  3. 將succ的前驅指向當前節點
  4. pred爲空,說明此時succ爲首節點,first指向當前節點;否則,將succ之前的前驅的後繼指向當前節點

5. 獲取/查詢元素

get(int index)方法

根據索引獲取鏈表中的元素。

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

// 檢測index合法性
private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 根據index 獲取元素
Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        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方法上文有詳細講解,這裏不做介紹。

getFirst()方法

獲取頭節點。

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

獲取尾節點。

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

6. 刪除元素

remove(Object o)

根據Object對象刪除元素。

public boolean remove(Object o) {
    // 如果o是空
    if (o == null) {
        // 遍歷鏈表查找 item==null 並執行unlink(x)方法刪除
        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;
}

E unlink(Node<E> x) {
    // assert x != null;
    // 保存x的元素值
    final E element = x.item;
    //保存x的後繼
    final Node<E> next = x.next;
    //保存x的前驅
    final Node<E> prev = x.prev;

    //如果前驅爲null,說明x爲首節點,first指向x的後繼
    if (prev == null) {
        first = next;
    } else {
        //x的前驅的後繼指向x的後繼,即略過了x
        prev.next = next;
        // x.prev已無用處,置空引用
        x.prev = null;
    }

    // 後繼爲null,說明x爲尾節點
    if (next == null) {
        // last指向x的前驅
        last = prev;
    } else {
        // x的後繼的前驅指向x的前驅,即略過了x
        next.prev = prev;
        // x.next已無用處,置空引用
        x.next = null;
    }
    // 引用置空
    x.item = null;
    size--;
    modCount++;
    // 返回所刪除的節點的元素值
    return element;
}

  1. 遍歷鏈表查找 item==null 並執行unlink(x)方法刪除
  2. 如果前驅爲null,說明x爲首節點,first指向x的後繼,x的前驅的後繼指向x的後繼,即略過了x.
  3. 如果後繼爲null,說明x爲尾節點,last指向x的前驅;否則x的後繼的前驅指向x的前驅,即略過了x,置空x.next
  4. 引用置空:x.item = null
  5. 圖解:


remove(int index)方法

根據鏈表的索引刪除元素。

public E remove(int index) {
    checkElementIndex(index);
    //node(index)會返回index對應的元素
    return unlink(node(index));
}

E unlink(Node<E> x)  方法上文有詳解。
removeFirst()方法

刪除頭節點。

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

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    //取出首節點中的元素
    final E element = f.item;
    //取出首節點中的後繼
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // first指向前first的後繼,也就是列表中的2號位
    first = next;
    //如果此時2號位爲空,那麼列表中此時已無節點
    if (next == null)
        //last指向null
        last = null;
    else
        // 首節點無前驅 
        next.prev = null;
    size--;
    modCount++;
    return element;
}

原理與添加頭節點類似。

removeLast()方法

刪除尾節點(last)

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

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    // 取出尾節點中的元素
    final E element = l.item;
    // 取出尾節點中的後繼
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    // last指向前last的前驅,也就是列表中的倒數2號位
    last = prev;
    // 如果此時倒數2號位爲空,那麼列表中已無節點
    if (prev == null)
        // first指向null
        first = null;
    else
        // 尾節點無後繼
        prev.next = null;
    size--;
    modCount++;
    // 返回尾節點保存的元素值
    return element;
}

7. 修改元素

修改元素比較簡單,先找到index對應節點,然後對值進行修改。

public E set(int index, E element) {
    checkElementIndex(index);
    // 獲取到需要修改元素的節點
    Node<E> x = node(index);
    // 保存之前的值
    E oldVal = x.item;
    // 執行修改
    x.item = element;
    // 返回舊值
    return oldVal;
}

8. 與ArrayList的對比

優點:

  1. 不需要擴容和預留空間,空間效率高
  2. 增刪效率高

缺點:

  1. 隨機訪問時間效率低
  2. 改查效率低

GitHub地址: https://github.com/fantj2016/java-reader

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章