Java 容器系列(六):LinkedList 原始碼分析02

cerish404發表於2020-11-16

1. 刪除類

1.1 E remove()、E removeFirst()

  • remove() 底層呼叫 removeFirst(),移除第一個元素,會報 NoSuchElementException 異常
public E remove() {
    return removeFirst();
}
/* 與 pop() 原始碼一直,會報 NoSuchElementException 異常 */
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

1.2 E remove(int index)

  • 移除指定位置的元素並返回該值
public E remove(int index) {
	/* 檢查 index 範圍 */
    checkElementIndex(index);
    /* node(index)獲取指定位置的元素,01篇有講解,需要的話可以返回去看 */
    return unlink(node(index));
}
E unlink(Node<E> x) {
    /* 得到 x 元素的值, next 節點,prev 節點 */
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
	/* prev == null 證明刪除的節點 x 是頭結點,將 next置為頭結點 */
    if (prev == null) {
        first = next;
    } else {
    	/* prev 節點的next節點為 x 的next節點,x 的prev節點置為null,方便GC */
        prev.next = next;
        x.prev = null;
    }
	/* last == null 證明刪除的節點 x 是尾結點,將 prev 置為尾結點 */
    if (next == null) {
        last = prev;
    } else {
    	/* next 節點的prev節點為 x 的prev節點,x 的next節點置為null,方便GC */
        next.prev = prev;
        x.next = null;
    }
	/* 將 x.item 置為 null,方便GC,更新 size、modCount 的值,返回被刪除的值 */
    x.item = null;
    size--;
    modCount++;
    return element;
}

1.3 boolean remove(Object o)

  • 移除指定的元素,如果存在則返回true,不存在則返回 false
/* 分 null 與非null 兩路前序查詢,找到之後執行 unlink(x); 此處可回看1.2 */
public boolean remove(Object o) {
    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;
}

1.4 E removeLast()

  • 與pollLast() 作用一直,不過 removeLast() 會報錯 NoSuchElementException
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

1.5 boolean removeFirstOccurrence(Object o)

  • 刪除第一個出現的元素,實際上底層呼叫的是 remove(Object o)
public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

1.6 boolean removeLastOccurrence(Object o)

  • 後面遍歷刪除出現的元素,底層呼叫 unlink(Node x)
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;
}

1.7 boolean removeAll(Collection<?> c)

  • 刪除與 集合 c 相同的元素,並返回true,沒發生刪除,返回 false
public boolean removeAll(Collection<?> c) {
	/* null 檢查 */
    Objects.requireNonNull(c);
    /* 有發生remove 操作才會改變該狀態 */
    boolean modified = false;
    /* 使用 迭代器iterator 遍歷,刪除 list 與 c 同時存在的元素 */
    Iterator<?> it = iterator(); /* 這裡省略了 this */
    while (it.hasNext()) {
        if (c.contains(it.next())) {
            it.remove();
            modified = true;
        }
    }
    return modified;
}

1.8 boolean retainAll(Collection<?> c)

  • 刪除與 集合 c 不相同的元素,並返回true,若沒發生刪除,返回 false
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    boolean modified = false;
    Iterator<E> it = iterator();
    while (it.hasNext()) {
    	/* 與 removeAll() 的唯一區別就是這裡 !c.contains(it.next()) */
        if (!c.contains(it.next())) {
            it.remove();
            modified = true;
        }
    }
    return modified;
}

1.9 總結

  • remove() 底層呼叫 removeFirst(),會報 NoSuchElementException 異常
  • remove(int index) 與 remove(Object o) 都是先找到指定的 Node,然後呼叫 unlink(Node x)
  • 學會 linkedList 的主要思想,prev,next指標 決定元素的順序,若查詢第 index 個,則只需 index 次 x = x.next(),若刪除,將 x節點 的prev,next指標指向的node節點記錄,斷開 x節點 的 prev,next指標,將 prev節點 的 next指標執向 next節點,將 next節點的prev指標指向 prev節點
  • 程式碼示例
import java.util.ArrayList;
import java.util.LinkedList;

public class RemoveTest {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for(int i = 1; i <= 15; i++) {
            linkedList.addLast(i * 10);
        }
        System.out.println("now linkedList: " + linkedList);

        System.out.println("linkedList.remove(new Integer(10)): " + linkedList.remove(new Integer(10)));
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.remove(0) " + linkedList.remove(0));
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.remove():  " + linkedList.remove());
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.removeFirst():  " + linkedList.removeFirst());
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.removeFirstOccurrence(new Integer(100)): " + linkedList.removeFirstOccurrence(new Integer(100)));
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.removeLast(): " + linkedList.removeLast());
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.removeLastOccurrence(new Integer(90)): " +linkedList.removeLastOccurrence(new Integer(90)));
        System.out.println("now linkedList: " + linkedList);

        ArrayList<Integer> arrayList01 = new ArrayList<Integer>() {{
            add(50);
            add(60);
        }};
        ArrayList<Integer> arrayList02 = new ArrayList<Integer>() {{
            add(70);
            add(80);
        }};
        System.out.println("linkedList.removeAll(arrayList01) : " + linkedList.removeAll(arrayList01));
        System.out.println("now linkedList: " + linkedList);
        System.out.println("linkedList.retainAll(arrayList02) : " + linkedList.retainAll(arrayList02));
        System.out.println("now linkedList: " + linkedList);

        System.out.println("linkedList.removeIf((e) -> e % 20 == 0): " + linkedList.removeIf((e) -> e % 20 == 0));
        System.out.println("now linkedList: " + linkedList);
        linkedList.replaceAll((e) -> e * 10);
        System.out.println("replaceAll((e) -> e * 10), now linkedList: " + linkedList);

    }
}

在這裡插入圖片描述

2. LinkedList 的 迭代器 Iterator

2.1 ListItr 列表迭代器

  • ListItr
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned; // 最後一個返回的節點
    private Node<E> next; // 下一個返回的節點
    private int nextIndex; // 下一個返回節點的index
    private int expectedModCount = modCount; // modCount 修改數
	/* 從index位開始的迭代器 */
    ListItr(int index) {
        /* 外部呼叫前,已經檢查過 index 範圍 */
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
	/* 是否有下一位元素 */
    public boolean hasNext() {
        return nextIndex < size;
    }
	/* 獲取下一位元素 */
    public E next() {
    	/* 檢查 modCount 版本 */
        checkForComodification();
        /* 沒有下一位即當前 index >= size,呼叫報錯 NoSuchElementException */
        if (!hasNext())
            throw new NoSuchElementException();
		/* 當前遍歷器已經記錄 next,將 next 彈出,所以下一個變成 next.next,nextIndex也需要++ */
        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }
	/* 是否有前一位,nextIndex <= 0 表示列表只有一個元素了 */
    public boolean hasPrevious() {
        return nextIndex > 0;
    }
	/* 獲取前一位元素 */
    public E previous() {
    	/* 檢測 modCount 版本,確認沒有被修改 */
        checkForComodification();
        /* 沒有前置元素,報錯 NoSuchElementException */
        if (!hasPrevious())
            throw new NoSuchElementException();
		/* 分為兩部分看
		 * 1. next = (next == null) ? last : next.prev;若next==null,則上一個返回的是 last,若不是,將next.prev返回
		 * 2. lastReturned = next 將最後一個返回值設定為 next,即獲取 previous 後,將該值設定為 next(下一個要返回的值)
		 */
        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }
	/* 下一個要返回的值的下標 */
    public int nextIndex() {
        return nextIndex;
    }
	/* 上一個返回值的下標 */
    public int previousIndex() {
        return nextIndex - 1;
    }
	/* 刪除最後一個返回的值 */
    public void remove() {
    	/* 版本檢查 */
        checkForComodification();
        /* 如果 lastReturned == null 即沒有彈過值 */
        if (lastReturned == null)
            throw new IllegalStateException();
        Node<E> lastNext = lastReturned.next;
        /* 呼叫 linkedlist 的 unlink(Node x) */
        unlink(lastReturned);
        /* 因為可能呼叫過 previous,出現 next == lastReturned,lastReturned 被刪除後需要指向下一位,否則只需下標前移一位 */
        if (next == lastReturned)
        	/* 解釋下這裡為什麼不需要nextIndex--,因為假設本來 nextIndex 指向被刪除的 lastReturned的下標為2,被刪除後,next上位,這時候next的下標變為2,所以 需要nextIndex-- */
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }
	/* null 與 modCount 檢測,通過設定 lastReturned.item = e; 即可 */
    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }
	/* 在 next 位置新增一個元素 */
    public void add(E e) {
        checkForComodification();
        /* 設定最後一個返回的值為null,可能是規定吧,我也不知道為什麼要這麼做 */
        lastReturned = null;
        /* next == null 即現在的index 為 size */
        if (next == null)
            linkLast(e);
        else
        	/* 在 next 位置新增元素e */
            linkBefore(e, next);
        /* 原本指向的 next 不變,所以需要nextIndex++ */    
        nextIndex++;
        expectedModCount++;
    }
	/* 對遍歷器剩下的元素進行 action 操作 */
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        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();
    }
}
  • 程式碼示例
import java.util.LinkedList;
import java.util.ListIterator;

public class ListIteratorTest {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for(int i = 1; i <= 8; i++) {
            linkedList.add(i * 10);
        }
        System.out.println("now linkedList: " + linkedList);
        ListIterator<Integer> listIterator = linkedList.listIterator(0);
        System.out.println("listIterator.next(): " + listIterator.next());
        System.out.println("listIterator.nextIndex(): " + listIterator.nextIndex());
        listIterator.add(100);
        System.out.println("now linkedList: " + linkedList);
        System.out.println("add(100)後,不會影響本來想取得下一個值 ,listIterator.nextIndex(): " + listIterator.nextIndex());
        System.out.println("listIterator.next(): " + listIterator.next());
        listIterator.add(120);
        System.out.println("now linkedList: " + linkedList);
        System.out.println("listIterator.hasPrevious(): " + listIterator.hasPrevious());
        System.out.println("listIterator.previous(): " + listIterator.previous());
        System.out.println("listIterator.next(): " + listIterator.next());
    }
}

在這裡插入圖片描述

2.2 DescendingIterator 倒序迭代器

  • new ListItr(size()) 得到 nextIndex 為 size 的 列表迭代器,所謂倒序迭代器,只是邏輯上的倒序,對於列表本身,由於雙向指向的存在,結構無需改變
private class DescendingIterator implements Iterator<E> {
    privatefinal ListItr itr = new ListItr(size());
    /* 是否有後序元素,呼叫的是 listItr 的是否有前序節點 */
    public boolean hasNext() {
        return itr.hasPrevious();
    }
    /* 獲取 next 節點,呼叫的是 listItr.previous 獲取list前序節點 */
    public E next() {
        return itr.previous();
    }
    /* 移除函式一樣 */
    public void remove() {
        itr.remove();
    }
}
  • 示例程式碼
import java.util.Iterator;
import java.util.LinkedList;

public class DescendingIteratorTest {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for(int i = 1; i <= 8; i++) {
            linkedList.add(i * 10);
        }
        System.out.println("now linkedList: " + linkedList);
        Iterator<Integer> descendingIterator = linkedList.descendingIterator();
        System.out.println("descendingIterator.hasNext(): " + descendingIterator.hasNext());
        System.out.println("descendingIterator.next(): " + descendingIterator.next());
    }
}

在這裡插入圖片描述

2.3 LLSpliterator 分割迭代器

  • LinkedList 內建的分割器,可將 連結串列分割成多部分
static final class LLSpliterator<E> implements Spliterator<E> {
    static final int BATCH_UNIT = 1 << 10;  // batch array size increment
    static final int MAX_BATCH = 1 << 25;  // max batch array size;
    final LinkedList<E> list; // linkedList 列表
    Node<E> current;      // 當前操作的 node 節點
    int est;              // 剩下未遍歷的大致個數,初始為 -1
    int expectedModCount; // 期望的 modCount 值,初始為 0
    int batch;            // batch size for splits
    LLSpliterator(LinkedList<E> list, int est, int expectedModCount) {
        this.list = list;
        this.est = est;
        this.expectedModCount = expectedModCount;
    }
	/* 若沒有初始化過,強制初始化 */
    final int getEst() {
        int s;
        final LinkedList<E> lst;
        /* est < 0 表示沒有被初始化過 */
        if ((s = est) < 0) {
        	/* 若list==null 則 est 個數為 0 */
            if ((lst = list) == null)
                s = est = 0;
            else {
            	/* 初始化 modCount,將 current 設定為 first,est 個數設定為 list的 size */
                expectedModCount = lst.modCount;
                current = lst.first;
                s = est = lst.size;
            }
        }
        /* 返回 est 個數 */
        return s;
    }
	/* 返回 est 個數,底層呼叫 getEst(),最後就只是進行了long型別的強轉 */
    public long estimateSize() { return (long) getEst(); }
	/* 嘗試分割 list */
    public Spliterator<E> trySplit() {
        Node<E> p;
        /* 獲取 est 的個數,這裡會把 current 初始化為 lst.first */
        int s = getEst();
        if (s > 1 && (p = current) != null) {
            int n = batch + BATCH_UNIT;
            if (n > s)
                n = s;
            if (n > MAX_BATCH)
                n = MAX_BATCH;
            Object[] a = new Object[n];
            int j = 0;
            /* 將 Spliterator 的 List 的元素逐個存進 a 陣列 */
            do { a[j++] = p.item; } while ((p = p.next) != null && j < n);
            /* 遍歷完成之後,將 current 設定為 p 即 null */
            current = p;
            /* 用於分割的個數 */
            batch = j;
            /* 當前遍歷器中剩餘未遍歷的個數 */
            est = s - j;
            /* 底層呼叫 ArraySpliterator,即嘗試分割後,得到的是 ArraySpliterator迭代器 */
            return Spliterators.spliterator(a, 0, j, Spliterator.ORDERED);
        }
        return null;
    }
	/* 將遍歷剩下的元素執行 action 操作 */
    public void forEachRemaining(Consumer<? super E> action) {
        Node<E> p; int n;
        if (action == null) throw new NullPointerException();
        if ((n = getEst()) > 0 && (p = current) != null) {
            current = null;
            est = 0;
            do {
                E e = p.item;
                p = p.next;
                action.accept(e);
            } while (p != null && --n > 0);
        }
        if (list.modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
	/* 對下一個即將遍歷的元素進行 accept 操作 */
    public boolean tryAdvance(Consumer<? super E> action) {
        Node<E> p;
        if (action == null) throw new NullPointerException();
        if (getEst() > 0 && (p = current) != null) {
            --est;
            E e = p.item;
            current = p.next;
            action.accept(e);
            if (list.modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return true;
        }
        return false;
    }

    public int characteristics() {
        return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
    }
}
  • 程式碼示例,注意呼叫 trySplit() 後,使用的是 ArraySpliterator 迭代器
import java.util.LinkedList;
import java.util.Spliterator;

public class SpliteratorTest {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(10);
        linkedList.add(20);
        linkedList.add(30);
        linkedList.add(40);
        linkedList.add(50);
        linkedList.add(60);

        Spliterator<Integer> spliterator = linkedList.spliterator();
        System.out.println("spliterator.getClass(): " + spliterator.getClass());
        System.out.println("spliterator.estimateSize(): " + spliterator.estimateSize());
        spliterator.tryAdvance((e) -> System.out.println(e));
        System.out.println("spliterator.estimateSize(): " + spliterator.estimateSize());
        System.out.println("================================================");
        Spliterator<Integer> integerSpliterator = spliterator.trySplit();
        integerSpliterator.tryAdvance((e) -> System.out.println(e));
        System.out.println("spliterator.estimateSize(): " + spliterator.estimateSize());
        System.out.println("integerSpliterator.estimateSize(): " + integerSpliterator.estimateSize());
        System.out.println("integerSpliterator.getClass(): " + integerSpliterator.getClass());
        Spliterator<Integer> integerSpliterator01 = integerSpliterator.trySplit();
        System.out.println("integerSpliterator.estimateSize(): " + integerSpliterator.estimateSize());
        System.out.println("integerSpliterator01.estimateSize(): " + integerSpliterator01.estimateSize());
        System.out.println("integerSpliterator01.getClass(): " + integerSpliterator01.getClass());
        System.out.println("====== 分割完成後,各個遍歷器的未遍歷個數 =====");
        System.out.println("spliterator.estimateSize(): " + spliterator.estimateSize());
        System.out.println("integerSpliterator.estimateSize(): " + integerSpliterator01.estimateSize());
        System.out.println("integerSpliterator01.estimateSize(): " + integerSpliterator01.estimateSize());
    }
}

在這裡插入圖片描述

3. 總結

  • 若在遍歷時,需要對 LinkedList 的個數進行改動的話,需要使用 遍歷器 Iterator,切記不能使用for迴圈,否則報錯 ConcurrentModificationException
  • LinkedList 內部有三個迭代器,列表迭代器 ListItr、 反序迭代器 DescendingIterator、分割迭代器LLSpliterator(在操作時,呼叫trySplit() 時使用ArraySpliterator)

相關文章