【Java入門提高篇】Day27 Java容器類詳解(九)LinkedList詳解


  這次介紹一下List接口的另一個踐行者——LinkedList,這是一位集諸多技能於一身的List接口踐行者,可謂十八般武藝,樣樣精通,棧、隊列、雙端隊列、鏈表、雙向鏈表都可以用它來模擬,話不多說,趕緊一起來看看吧。

  本篇將從以下幾個方面對LinkedList進行解析:

  1.LinkedList整體結構。

  2.LinkedList基本操作使用栗子。

  3.LinkedList與ArrayList的對比分析。

  4.LinkedList整體源碼分析。

LinkedList整體結構

  先來看看LinkedList中的結構,LinkedList跟ArrayList不一樣,ArrayList中是動態維護了一個數組,所有的操作都是 在該數據上進行操作,而LinkedList中其實是一個個的Node節點,每個Node節點首尾相連。如果你還記得前幾篇的內容的話,就應該會想起HashMap中其實也是有Node節點的,但兩者還是有比較多不一樣的地方,先來看看LinkedList中的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;
        }
    }

  嗯,其實很簡單,里面只有三個成員變量,item用來存儲具體的元素信息,next指向下一個Node節點,prev指向上一個Node節點,node節點之間通過next和prev相連接,組成了一個雙向鏈表的形式。

  嗯,看圖說話應該就很好懂了,LinkedList正是由Node這樣首尾相連組成了鐵索連環的格局。而HashMap中的Node節點其實是一個單鏈表的節點,只有指向后一個節點的引用(next),並沒有指向前一個節點的引用,回顧一下HashMap的Node節點代碼便能發現這一點。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

       ......省略部分代碼
    }

LinkedList基本操作使用栗子

  接下來看看LinkedList中的一些基礎操作,以下是一個小栗子: 

ublic class LinkedListTest {

    public static void main(String[] args){
        // 定義要插入集合的字符串對象
        String a = "A", b = "B", c = "C", d = "D", e = "E", f = "F", g = "G";
        // 創建List集合
        LinkedList<String> list = new LinkedList<>();

        // add操作 添加元素
        list.add(a);
        list.add(b);
        list.add(c);
        list.add(f);

        // 迭代器遍歷
        System.out.println("修改前:");
        traverseListByIterator(list);

        // set操作
        // 將索引位置為1的對象修改為對象g
        list.set(1, g);
        // 將索引位置為2的對象修改為對象d
        list.set(2, e);

        // 新建迭代器進行遍歷(注意:迭代器是一次性使用的,遍歷到列表尾部之后,無法重置,再次遍歷時需要新建迭代器)
        System.out.println();
        System.out.println("set修改后的集合中的元素是:");
        traverseListByIterator(list);

        // 創建ArrayList
        List<String> strings = new ArrayList<>();
        strings.add(d);
        strings.add(a);
        strings.add(f);

        // addAll添加所有元素
        list.addAll(strings);

        //foreach方式遍歷
        System.out.println("addAll修改后的集合中的元素是:");
        traverseListByForEach(list);

        // remove移除元素
        String removeElement = list.remove();
        System.out.println("移除的元素是:" + removeElement);
        System.out.println("remove修改后的集合中的元素是:");
        traverseListByForEach(list);

        // add插入元素(在第四個位置插入元素"gg")
        list.add(3, "gg");
        System.out.println("add插入元素后的集合中的元素是:");
        traverseListByForEach(list);
    }

    /**
     * foreach方式遍歷列表
     * @param list 待遍歷的列表
     */
    private static <T> void traverseListByForEach(List<T> list){
        for (T t : list){
            System.out.print(t + " ");
        }
        System.out.println();
    }

    /**
     * 迭代器方式遍歷列表
     * @param list 待遍歷的列表
     */
    private static <T> void traverseListByIterator(List<T> list){
        Iterator<T> iterator = list.iterator();
        // 循環獲取列表中的元素
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
    }

}

  這里僅僅演示了LinkedList的鏈表操作,主要有add、addAll、remove、set等,幾乎每個常用方法都有重載方法,比如只有一個參數的add方法,會直接將該元素插入到列表的尾部,而有兩個參數的add方法,會將元素插入指定位置。

LinkedList與ArrayList的對比分析

  也許你會問,ArrayList不是也挺好用的嗎,那些操作ArrayList也能做啊,為什么還要用LinkedList呢?

  這是一個好問題,ArrayList的最大特點就是能隨機訪問,因為元素在物理上是連續存儲的,所以訪問的時候,可以通過簡單的算法直接定位到指定位置,所以不管隊列的元素數量有多少,總能在O(1)的時間里定位到指定位置,但是連續存儲也是它的缺點,導致要在中間插入一個元素的時候,所有之后的元素都要往后挪動。而LinkedList的插入只需要調整前后元素的引用即可。

  看圖說話,ArrayList插入顯然需要進行更多的賦值操作,特別是數組元素個數比較多的時候,會更加明顯,如果剛好需要擴容的話,那就會更慢了。而LinkedList只需要將插入位置的前后元素的next或prev引用進行調整即可,而且也沒有擴容問題,因為它本身就沒有容量的概念,理論上可以無限添加元素。

  然后來實際對比一下效率差距:

public abstract class TimeCounter {
    private String name;

    TimeCounter(String name){
        this.name = name;
    }

    public void count(){long time = System.currentTimeMillis();
        doSomething();
        System.out.println(name + " 耗時:" + (System.currentTimeMillis() - time));
    }

    protected abstract void doSomething();
}

  先將這個計時抽象成一個模板,每個需要統計耗時的操作只需要繼承該類,然后重寫doSomeThing方法即可。

public class Test {
    public static void main(String[] args){
        TimeCounter arrayListAddCounter = new TimeCounter("ArrayList add插入到末尾:") {
            private List<Integer> list = new ArrayList<>();

            @Override
            protected void doSomething() {
                for (int i = 0; i < 100000; i++) {
                    list.add( i);
                }
            }
        };

        TimeCounter linkedListAddCounter = new TimeCounter("LinkedList add插入到末尾:") {
            private List<Integer> list = new LinkedList<>();

            @Override
            protected void doSomething() {
                for (int i = 0; i < 100000; i++) {
                    list.add( i);
                }
            }
        };

        arrayListAddCounter.count();
        linkedListAddCounter.count();
    }
}
ArrayList add插入到末尾: 耗時:11
LinkedList add插入到末尾: 耗時:9

  好像勉強符合預期,LinkedList比ArrayList略微快一點,其實如果在ArrayList容量足夠的情況下,ArrayList的插入元素到末尾操作是要比LinkedList插入要快的,因為它只需要進行一次賦值即可,而LinkedList還需要先new一個新節點然后再接到鏈表的最后,這個new的過程看起來微不足道,但是一旦循環次數到達一定量級,開銷是不可忽略的。例如把上面的循環次數從100000改成1000000,結果就會變成這樣:

ArrayList add插入到末尾: 耗時:40
LinkedList add插入到末尾: 耗時:768

  這時候,創建節點的開銷成了主要時間開銷,效率甚至不如ArrayList。我們再換一個插入方式,上面是把元素插入到末尾,這次來把元素插入到首端看看:

public class Test {
    public static void main(String[] args){
        TimeCounter arrayListAddCounter = new TimeCounter("ArrayList add插入到首端:") {
            private List<Integer> list = new ArrayList<>();

            @Override
            protected void doSomething() {
                for (int i = 0; i < 100000; i++) {
                    list.add(0, i);
                }
            }
        };

        TimeCounter linkedListAddCounter = new TimeCounter("LinkedList add插入到首端:") {
            private List<Integer> list = new LinkedList<>();

            @Override
            protected void doSomething() {
                for (int i = 0; i < 100000; i++) {
                    list.add(0, i);
                }
            }
        };

        arrayListAddCounter.count();
        linkedListAddCounter.count();
    }
}
ArrayList add插入到首端: 耗時:607
LinkedList add插入到首端: 耗時:11

  當每次都插入在首端時,ArrayList每次都需要進行元素移動,而且列表中元素越多,需要進行移動的次數也越多,在這種情況下,LinkedList是明顯優於ArrayList的。

  所以說,其實這兩者是各有所長各有所短的,一般情況下,選ArrayList就好,除非是需要進行循環操作的次數到達了萬的量級的時候,才需要對兩者進行選擇。也許你會說,既然一般情況下,兩者的效率差別不大,那直接用ArrayList就好了,說這么多干嘛呢。哈哈,如果是這樣想,那就大錯特錯了。首先我們不僅需要知其然,還需要知其所以然,如果僅僅知道LinkedList比ArrayList插入效率高,但是卻不知道為什么高,高多少,是遠遠不夠的。其次,我們不能僅僅考慮正常情況,對於極端情況也需要有預防措施,對極端情況的思考,正是高手與新手的最大區別。

  下面再看看查找元素的比較:

public class Test {
    public static void main(String[] args){
        TimeCounter arrayListAddCounter = new TimeCounter("ArrayList get遍歷元素:") {
            private List<Integer> list = new ArrayList<>();

            {
                for (int i = 0; i < 100000; i++) {
                    list.add(i);
                }
            }

            @Override
            protected void doSomething() {
                for (int i = 0; i < 100000; i++) {
                    list.get(i);
                }
            }
        };

        TimeCounter linkedListAddCounter = new TimeCounter("LinkedList get遍歷元素:") {
            private List<Integer> list = new LinkedList<>();

            {
                for (int i = 0; i < 100000; i++) {
                    list.add(i);
                }
            }

            @Override
            protected void doSomething() {
                for (int i = 0; i < 100000; i++) {
                    list.get(i);
                }
            }
        };

        arrayListAddCounter.count();
        linkedListAddCounter.count();
    }
}
ArrayList get遍歷元素: 耗時:3
LinkedList get遍歷元素: 耗時:4484

  ArrayList完勝,可以看出LinkedList中查找元素是一個十分耗時的操作,甚至比插入元素耗時還要長,因為每次get的時候都是從鏈表兩端進行逐個查找,直到找到指定的位置。想要知道具體細節的話,那就耐心的看看下面的源碼解析吧。

LinkedList整體源碼分析

  先來看看LinkedList的整體結構:

 

  可以看到LinkedList有四個內部類,分別是Node、ListItr、DescendingIterator、LLSpliterator。Node類主要用於存放元素,先來看看Node:

    private static class Node<E> {
        //存放元素
        E item;
        //指向下一個Node節點
        Node<E> next;
        //指向上一個Node節點
        Node<E> prev;

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

  這是可以說是最簡單的類了,也構成了LinkedList的基礎,LinkedList正是由一個個Node節點連接組成。再來看看ListItr類:

    //列表迭代器類
    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

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

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            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();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        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();
        }
    }

  這個類是LinkedList的迭代器類,主要用於順序遍歷LinkedList,在前面的栗子中有使用迭代器的hasNext方法和next方法,其實它們的實現都很簡單,hasNext只是簡單的比較下一個要訪問的元素序號是否大於列表中的元素個數,next方法則是將next的引用賦值給lastReturned變量,然后將next指向其下一個節點並且將index加1。但跟普通迭代器不一樣的地方在於這個迭代器不僅可以正序遍歷,還可以使用previous方法進行倒序遍歷,DescendingIterator便是使用了迭代器的previous方法進行便利的。

    /**
     * 降序迭代器
     */
    private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
    }

  這個迭代器就比較簡單了,只是包裹了一個ListItr實例,然后重載了Iterator的幾個方法。LLSpliterator是用於並行流的可分割式迭代器,這里就不做過多介紹了。

  再來看看常用的幾個方法的實現:

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

    /**
     * 把元素e鏈接成尾節點
     */
    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++;
    }

  當插入一個元素時,會new一個Node對象,並將該值放入Node節點中,然后掛到鏈表的尾部。

    /**
     * 在列表指定位置插入指定元素
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    /**
     * 插入一個元素到指定非空節點之前
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

  add的重載版本,可以指定序號進行插入,將元素插入到鏈表中間,進行的操作過程可以聯系前面的圖進行理解。

    /**
     * 返回指定位置的元素
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    /**
     * 返回指定元素序號的非空節點。
     */
    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;
        }
    }

  可以看到get方法其實是從頭部或者尾部進行遍歷定位的,每次將x的引用向后/向前移動一位,當鏈表數據量比較大時,這個過程其實是很耗時間的,前面的對比中應該也能發現這一點。

    /**
     * 取回並刪除此列表的頭部(第一個元素)。
     */
    public E remove() {
        return removeFirst();
    }

    /**
     * 移除列表指定位置的元素,如果其后子序列存在,則將其元素左移,將它們的序號減一。
     * 返回列表中移除的元素。
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    /**
     * 移除並返回首節點
     */
    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 = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 移除非空節點x
     */
    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;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

  remove方法也有兩個重載版本,不帶參數的remove方法僅僅移除並返回最后一個節點,而指定序號參數的remove方法則會移除並返回鏈表中指定位置的節點。

  此外鏈表還有可以用於隊列的諸多方法:

  • 在尾部添加元素:(add, offer),add()會在長度不夠時拋出異常:IllegalStateException;  offer()則不會,只返回false。
  • 查看頭部元素 (element, peek),返回頭部元素,但不改變隊列。element()會在沒元素時拋出異常:NoSuchElementException;  peek()返回null;
  • 刪除頭部元素 (remove, poll),返回頭部元素,並且從隊列中刪除,remove()會在沒元素時拋出異常:NoSuchElementException;  poll()返回null; 

  即可以通過以上方法來實現單向隊列的操作,也可以使用addFirst,addLast,removeFirst,removeLast方法來實現雙向隊列的操作。以下是一個簡單隊列的實現:

public class MyQueue<T> {
    private LinkedList<T> list = new LinkedList<T>();

    //清空隊列
    public void clear() {
        list.clear();
    }

    //判斷隊列是否為空
    public boolean isEmpty() {
        return list.isEmpty();
    }

    //進隊
    public void enqueue(T o) {
        list.addLast(o);
    }

    //出隊
    public T dequeue(){
        if(!list.isEmpty())
        {
            return list.removeFirst();
        }
        return null;
    }

    //獲取隊列長度
    public int length() {
        return list.size();
    }

    //查看隊首元素
    public T peek() {
        return list.getFirst();
    }
    
    //測試隊列
    public static void main(String[] args) {
        MyQueue<String> queue = new MyQueue<String>();
        System.out.println(queue.isEmpty());
        queue.enqueue("a");
        queue.enqueue("b");
        queue.enqueue("c");
        queue.enqueue("d");
        queue.enqueue("e");
        queue.enqueue("f");
        System.out.println(queue.length());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.peek());
        System.out.println(queue.dequeue());
        queue.clear();
        queue.enqueue("s");
        queue.enqueue("t");
        queue.enqueue("r");
        System.out.println(queue.dequeue());
        System.out.println(queue.length());
        System.out.println(queue.peek());
        System.out.println(queue.dequeue());
    }
}

  嗯,其實就是借雞生蛋的事情嘛,哈哈。

  再來看看用LinkedList的簡單棧實現:

public class MyStack<T> {
    private LinkedList<T> stack = new LinkedList<T>();

    // 入棧
    public void push(T v) {
        stack.addFirst(v);
    }

    // 出棧,但不刪除
    public T peek() {
        return stack.getFirst();
    }

    // 出棧
    public T pop() {
        return stack.removeFirst();
    }

    // 棧是否為空
    public boolean empty() {
        return stack.isEmpty();
    }

    // 打印棧元素
    public String toString() {
        return stack.toString();
    }
}

  你看,其實很簡單吧,LinkedList提供的大量的方法,可以很方便的進行鏈表、雙向鏈表、隊列、雙端隊列、棧等數據結構的實現,可以說非常好用了。

  下面是LinkedList的全部源碼,行有余力的話可以選擇你想要了解的部分進行閱讀,如果有不懂的地方可以在本文后面留言,當然,也可以直接跳過,以后想要深入了解的時候再進行閱讀也不遲。

/**
 * 雙向鏈表實現了List接口和Deque接口,實現了多有可選List操作,並且允許放入所有的元素,包括null。
 *
 * 所有的操作都表現得像雙向鏈表,索引到列表中的操作將從開頭或者結尾遍歷列表,以較接近指定索引為准。
 *
 * 注意,這個實現類不是線程安全的,如果多個線程同時訪問一個鏈表,並且至少一個線程修改了鏈表的結構,
 * 則必須在外部實現同步。結構性修改指的是那些增加刪除一個或多個元素的操作,僅設置元素的值不是結構修改。
 * 這通常通過在封裝列表的某個對象上進行同步來實現。
 *
 * 如果沒有這樣的對象存在,則列表應該使用Collections#synchronizedList方法包裝,最好在創建列表的時候進行,
 * 以防止意外的非同步引用對列表進行了修改。
 *   List list = Collections.synchronizedList(new LinkedList(...));
 *
 * 該類的iterator方法和listIterator方法返回的迭代器是“fail-fast”的,如果列表在迭代器創建之后的任何時刻被進行
 * 結構性的修改了,則調用迭代器自身的remove或者add方法時將會拋出ConcurrentModificationException異常,因此
 * 當遇到並發修改時,迭代器會快速的失敗,而不是在未來某個不確定的時刻進行武斷冒險或不確定性的行為
 *
 * 注意,通常來說,不能保證迭代器的fail-fast機制,在遇到非同步的並發修改時,不可能做出任何嚴格的保證。
 * fail-fast 迭代器只能盡最大努力拋出ConcurrentModificationException異常,因此,如果程序依賴這個異常來
 * 進行正確性判斷是錯誤的,fail-fast機制僅應該用於檢測異常。
 *
 */

public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;

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

    /**
     * 指向最后一個節點
     * 恆等式: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    /**
     * 構造一個空的列表
     */
    public LinkedList() {
    }

    /**
     * 構造一個包含指定集合內所有元素的列表,存儲的順序為集合的迭代器訪問順序。
     * @throws NullPointerException 空指針異常
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    /**
     * 把元素e鏈接成首節點
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * 把元素e鏈接成尾節點
     */
    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++;
    }

    /**
     * 插入一個元素到指定非空節點之前
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

    /**
     * 移除首節點並返回該節點元素值
     */
    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 = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 移除尾節點並返回該節點元素值
     */
    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 = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 移除非空節點x
     */
    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;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 返回列表的首節點
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    /**
     * 返回列表的最后一個節點
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

    /**
     * 移除並返回首節點
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    /**
     * 移除並返回尾節點
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

    /**
     * 插入指定元素到列表首端
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

    /**
     * 擴展指定元素到列表尾端
     */
    public void addLast(E e) {
        linkLast(e);
    }

    /**
     * 返回列表中是否包含指定元素
     */
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    /**
     * 返回列表中元素個數
     */
    public int size() {
        return size;
    }

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

    /**
     * 如果該元素存在,則移除列表中首次出現的該指定元素,如果不存在,則原鏈表不會改變。
     * 如果該列表中包含該指定元素則返回true,否則返回false
     */
    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;
    }

    /**
     * 添加指定集合中的所有元素到列表的尾部,順序為指定集合的迭代器遍歷順序。如果該操作正在進行時,指定集合
     * 被修改了,那么該操作的行為是不可預測的。
     */
    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;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

    /**
     * 移除列表中所有元素
     */
    public void clear() {
        // 清除節點之間所有元素的鏈接不是必要的,但是:
        // - 如果被清除的節點處於不同代之間,可以幫助分代GC。
        // - 一定要釋放內存,即便有一個迭代器引用
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }


    // 位置訪問操作

    /**
     * 返回指定位置的元素
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    /**
     * 使用指定元素替換指定位置的元素
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

    /**
     * 在列表指定位置插入指定元素,如果該位置有任何元素,則將其后的子序列元素都往右移(將它們的序號都增加1)
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    /**
     * 移除列表指定位置的元素,如果其后子序列存在,則將其元素左移,將它們的序號減一。
     * 返回列表中移除的元素。
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    /**
     * 判斷該序號是否在列表中對應位置存在元素
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    /**
     * 判斷給參數是否是迭代器或者add操作的合法位置
     */
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    /**
     * 構造一個IndexOutOfBoundsException 異常的詳情消息,在錯誤處理代碼的許多可能的重構中,
     * 這個大綱在服務端和客戶端虛擬機中都表現的最好。
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(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;
        }
    }

    // 搜索操作

    /**
     * 返回指定元素在列表中第一次出現的位置,否則返回-1
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

    /**
     * 返回指定元素在鏈表中最后一次出現的位置,如果鏈表中不包含該元素,則返回-1
     */
    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

    // 隊列操作

    /**
     * 取回但不刪除此列表的頭部(第一個元素)。
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    /**
     * 取回但不刪除此列表的頭部(第一個元素)。
     * 如果不存在,則會拋出NoSuchElementException異常
     */
    public E element() {
        return getFirst();
    }

    /**
     * 取回並刪除此列表的頭部(第一個元素)。
     * 如果該鏈表為空,則返回null
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    /**
     * 取回並刪除此列表的頭部(第一個元素)。
     */
    public E remove() {
        return removeFirst();
    }

    /**
     * 添加指定元素到鏈表尾部
     */
    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;
    }

    /**
     *
     * 取回但是不刪除隊列的首元素。如果隊列為空則返回null。
     */
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    /**
     * 取回但是不刪除鏈表的最后一個元素,如果隊列為空,則返回null
     */
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

    /**
     * 取回並刪除隊列的首元素,如果隊列為空,則返回null
     */
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    /**
     * 取回並刪除隊列的尾元素,如果隊列為空,則返回null
     */
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

    /**
     * 在隊列前端插入元素
     */
    public void push(E e) {
        addFirst(e);
    }

    /**
     * 刪除並返回隊列的第一個元素,如果列表為空則拋出NoSuchElementException 異常
     */
    public E pop() {
        return removeFirst();
    }

    /**
     * 刪除此隊列中第一個出現的指定元素(從頭到尾的方式遍歷時),如果隊列中不包含該元素,則隊列不會改變。
     */
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

    /**
     * 刪除此隊列中最后一次出現的指定元素(從頭到尾的方式遍歷時),如果隊列中不包含該元素,則隊列不會改變。
     */
    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;
    }

    /**
     * 返回從指定位置開始,以正確的序列迭代該列表的迭代器.
     * 列表迭代器是“fail-fast”的,如果列表在迭代器創建之后的任何時刻被進行
     * 結構性的修改了,則調用迭代器自身的remove或者add方法時將會拋出ConcurrentModificationException異常,因此
     * 當遇到並發修改時,迭代器會快速的失敗,而不是在未來某個不確定的時刻進行武斷冒險或不確定性的行為
     *
     * 注意,通常來說,不能保證迭代器的fail-fast機制,在遇到非同步的並發修改時,不可能做出任何嚴格的保證。
     * fail-fast 迭代器只能盡最大努力拋出ConcurrentModificationException異常,因此,如果程序依賴這個異常來
     * 進行正確性判斷是錯誤的,fail-fast機制僅應該用於檢測異常。
     */
    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }

    //列表迭代器類
    private class ListItr implements ListIterator<E> {
        //記錄上一個返回的節點
        private Node<E> lastReturned;
        //指向下一個節點
        private Node<E> next;
        //下一個節點的序號
        private int nextIndex;
        //用於檢測遍歷過程中List是否被修改
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

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

        public E next() {
            //檢測是否修改
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            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();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        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();
        }
    }

    private static class Node<E> {
        //存放元素
        E item;
        //指向下一個Node節點
        Node<E> next;
        //指向上一個Node節點
        Node<E> prev;

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

    /**
     * 返回一個倒序遍歷的迭代器
     */
    public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }

    /**
     * 降序迭代器
     */
    private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
    }

    private LinkedList<E> superClone() {
        try {
            return (LinkedList<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

    /**
     * 淺克隆
     */
    public Object clone() {
        LinkedList<E> clone = superClone();

        // Put clone into "virgin" state
        clone.first = clone.last = null;
        clone.size = 0;
        clone.modCount = 0;

        // Initialize clone with our elements
        for (Node<E> x = first; x != null; x = x.next)
            clone.add(x.item);

        return clone;
    }

    /**
     * 返回一個包含列表所有元素的數組,元素的順序為從第一個到最后一個。
     */
    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
    }

    /**
     * 返回一個包含列表所有元素的數組,元素的順序為從第一個到最后一個。返回元素數組的類型
     * 與指定數組的類型一致。如果列表大小適合指定的數組,則返回該數組。 否則,將為新數組分配指定
     * 數組的運行時類型和此列表的大小。
     *
     * 如果列表的空間適合指定的數組(數組比列表有更多的元素),緊跟在列表末尾之后的數組中的元素設置
     * 為null(僅當調用者知道列表不包含任何null元素時,這在確定列表長度時很有用。)
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                    a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size)
            a[size] = null;

        return a;
    }

    private static final long serialVersionUID = 876323262645176354L;

    /**
     * 序列化
     */
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out size
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (Node<E> x = first; x != null; x = x.next)
            s.writeObject(x.item);
    }

    /**
     * 反序列化
     */
    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i = 0; i < size; i++)
            linkLast((E)s.readObject());
    }

    /**
     * 創建一個可分割的迭代器
     */
    @Override
    public Spliterator<E> spliterator() {
        return new LLSpliterator<E>(this, -1, 0);
    }

    /** Spliterators.IteratorSpliterator 的定制版本 */
    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; // null OK unless traversed
        Node<E> current;      // current node; null until initialized
        int est;              // size estimate; -1 until first needed
        int expectedModCount; // initialized when est set
        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; // force initialization
            final LinkedList<E> lst;
            if ((s = est) < 0) {
                if ((lst = list) == null)
                    s = est = 0;
                else {
                    expectedModCount = lst.modCount;
                    current = lst.first;
                    s = est = lst.size;
                }
            }
            return s;
        }

        public long estimateSize() { return (long) getEst(); }

        public Spliterator<E> trySplit() {
            Node<E> p;
            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;
                do { a[j++] = p.item; } while ((p = p.next) != null && j < n);
                current = p;
                batch = j;
                est = s - j;
                return Spliterators.spliterator(a, 0, j, Spliterator.ORDERED);
            }
            return null;
        }

        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();
        }

        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;
        }
    }

}

  最后,再對LinkedList做一個簡單的小結:

  LinkedList是由Node節點首尾相連而成的結構,相比ArrayList而言,在進行插入和刪除時不需要進行大量的元素移動,省去了元素復制的開銷,也不存在擴容開銷,但是每次添加節點都需要創建一個新的Node對象,所以當節點數量很多時,這部分對象將占用很大開銷,包括時間成本和空間成本,因此需要根據實際情況進行合理選擇。LinkedList因為提供了大量方便的獲取元素、插入元素和移除元素的方法,所以可以很方便的進行隊列、棧等數據結構的實現。

  到此,LinkedList就算講解完畢了,一不小心又寫了這么長,罪過罪過。。。下次還是多分幾篇寫吧,這么長的文章,確實不便於閱讀,面壁中。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM