LinkedList源碼分析-JDK1.8

1.概述

LinkedList是基於雙向鏈表來編寫的,不須要考慮容量的問題,但節點須要額外的空間存儲前驅和後繼的引用。有序可重複,可存儲null值,非線程安全。java

1.1繼承體系

LinkedList實現了Deque接口,這樣LinkedList就具有了雙端隊列的功能,本文旨在介紹LinkedListList功能,隊列功能再也不進行說明。node

LinkedList沒有實現RandomAccess接口,因此不要經過訪問下標方式(for(int i=0;i<size;i++))遍歷,不然效率極差。安全

2.源碼分析

本文主要針對LinkedList的經常使用操做進行分析,代碼以下。dom

List<String> linkedList = new LinkedList<>();
//add(E e)
linkedList.add("QQ");
linkedList.add("WW");
linkedList.add("EE");
linkedList.add("RR");
linkedList.add("TT");
linkedList.add("YY");
linkedList.add("UU");
linkedList.add("II");
linkedList.add("OO");
//add(int index, E element)插入元素到指定結點
linkedList.add(2, "BaseC");
//get
System.out.println(linkedList.get(2));
//traverse(遍歷)
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
iterator.next();
}
//remove(Object o)
linkedList.remove("EE");
//remove(int index)刪除中間元素
linkedList.remove(3);
//remove(int index)刪除鏈表頭元素
linkedList.remove(0);
//remove(int index)刪除鏈表尾元素
linkedList.remove(linkedList.size() - 1);
System.out.println(linkedList);

2.1屬性

//元素個數
transient int size = 0;

//鏈表首結點
transient Node<E> first;

//鏈表尾結點
transient Node<E> last;

//鏈表結點類
private static class Node<E> {
    //結點中的值
    E item;
    //指向以前的節點
    Node<E> prev;
    //指向以後的節點
    Node<E> next;

    //構造方法
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

2.2新增

2.2.1add(E e)

//將元素添加到列表尾部
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    //將e元素添加鏈表末端
    void linkLast(E e) {
        //聲明l變量保存當前last對象
        final Node<E> l = last;
        //以e爲item聲明一個新的last對象
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //第一次添加元素時
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

首次添加元素以下圖所示:源碼分析

非首次添加元素以下如圖所示:post

2.2.2add(int index, E element)

//將元素插入到指定index,以前在此index及其以後的元素向右移動。
public void add(int index, E element) {
    //判斷index是否在[0,size]之間,注意包含0和size。
    checkPositionIndex(index);
    //若是index等於當前size,調用linkLast(E e)方法,不然調用linkBefore(E e)方法
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

//返回指定index上的非空結點
Node<E> node(int index) {
    //假設index合法
    //size>>1返回size的一半,判斷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;
    }
}

//在非空的succ結點以前插入元素
void linkBefore(E e, Node<E> succ) {
    //獲取succ結點的prev結點
    final Node<E> pred = succ.prev;
    //在pred和succ之間生成item爲e的結點
    final Node<E> newNode = new Node<>(pred, e, succ);
    //將succ的prev指向newNode
    succ.prev = newNode;
    //若是succ是頭元素,將first指向newNode
    if (pred == null)
        first = newNode;
    else
        //不然將pred的next結點指向newNode
        pred.next = newNode;
    size++;
    modCount++;
}

添加流程以下圖所示:this

在鏈表首尾添加元素很高效,時間複雜度爲O(1)spa

在鏈表中間添加元素比較低效,時間複雜度爲O(N)線程

2.3查找

//返回指定位置上的值
public E get(int index) {
    //檢查index是否合法
    checkElementIndex(index);
    //node(int index)方法已在上文列出,此處不在展現。
    return node(index).item;
}

查找方法的重點在於node(int index)方法。因爲須要經過從頭或從尾查找元素,時間複雜度爲O(N)3d

2.4遍歷

在分析源碼以前,先看下iterator()的調用流程。首先調用linkedList.iterator()進入AbstractSequentialListiterator()方法。

public Iterator<E> iterator() {
    return listIterator();
}

此方法調用了其父類AbstractListlistIterator()方法。

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

而在此方法中調用了listIterator(final int index)方法,此方法LinkedList將其重寫了,因此程序就會去調用LinkedList中的listIterator(int index)方法。經過這個流程咱們也鞏固下Java多繼承下方法的調用流程。下面我們就來看源碼吧。

public ListIterator<E> listIterator(int index) {
    //判斷index是否在[0,size]之間,注意包含0和size。
    checkPositionIndex(index);
    return new ListItr(index);
}

private class ListItr implements ListIterator<E> {
    //上一次返回的值
    private Node<E> lastReturned;
    //當前要返回的值
    private Node<E> next;
    //當前index值
    private int nextIndex;
    //用於fail-fast校驗
    private int expectedModCount = modCount;

    ListItr(int index) {
        //若是index是當前size值,則next爲null,不然返回對應index的結點。
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        //fail-fast判斷
        checkForComodification();
        //判斷當前是否還有值,調用方可直接調用next()方法獲取下個值,沒必要額外進行hasNext()的判斷。
        if (!hasNext())
            throw new NoSuchElementException();

        //即將返回next,將next傳給lastReturned
        lastReturned = next;
        //獲取下次將要返回的結點
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }
    
    //省略其他代碼。。。
}

2.5刪除

2.5.1remove(Object o)

//刪除o在列表中第一次存儲的位置
public boolean remove(Object o) {
    //當o爲null時
    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;
}

//刪除非空結點
E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    //刪除頭元素時
    if (prev == null) {
        first = next;
    } else {
        //將prev結點的next指針指向next結點,將被刪除結點的prev置爲null
        prev.next = next;
        x.prev = null;
    }

    //刪除尾元素時
    if (next == null) {
        last = prev;
    } else {
        //將next結點的prev指針指向prev結點,將被刪除結點的next置爲null
        next.prev = prev;
        x.next = null;
    }

    //將x的item變爲null,便於GC
    x.item = null;
    size--;
    modCount++;
    return element;
}

unlink(Node x)其流程以下圖所示:

2.5.2remove(int index)

//刪除指定index處的元素,後面的元素向左移動一位,返回被刪除的元素。
public E remove(int index) {
    //檢驗index是否在[0,index)以內
    checkElementIndex(index);
    //經過node(int index)查找結點,將其傳入unlink(Node node)方法
    return unlink(node(index));
}

在鏈表首尾刪除元素很高效,時間複雜度爲O(1)

在鏈表中間刪除元素比較低效,時間複雜度爲O(N)

3.參考

LinkedList 源碼分析(JDK 1.8)

死磕 java集合之LinkedList源碼分析

相關文章
相關標籤/搜索