LinkedList源碼超詳細解析

LinkedList源碼分析

衆所周知,LinkedList實現了List接口,LinkedList適用於集合元素先入先出和先入後出的場景,在隊列的源碼中被頻繁使用,我們在使用一般的隊列時,就直接可以拿LinkedList來實現,如下:

        LinkedList<Integer> queue = new LinkedList<>();
        queue.addLast(3);  //相當於入隊操作
        queue.addLast(4);
        queue.addLast(5);
        System.out.println(queue.removeFirst());  //相當於出隊操作(poll)
        System.out.println(queue.getFirst());   //相當於peek操作

可見LinkedList的操作十分的靈活!!

1. 整體架構

LinkedList的底層結構是一個雙向鏈表,如下圖:
在這裏插入圖片描述
看到上圖的結構,注意到:

  • 當鏈表中沒有數據的時候,first和last是同一個節點,前後指向都爲零;
  • 雙向鏈表大小隻受內存大小的限制;

1. 類定義

先來看看這個類的定義:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

注意到了沒,他還實現了Deque這個接口,所以LinkedList中還實現了poll()、peek()、push()、pop()等等,這些方法都是實現了Deque這個接口裏的抽象方法,而這些方法的實現,就是基於LikedList裏面本來的add、remove系列操作,比如看push()和pop()和peek()這三個方法:

    public void push(E e) {
        addFirst(e);   //頭插法新增節點
    }
    public E pop() {  //刪除頭節點
        return removeFirst();
    }
    public E peek() {   //返回頭節點
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

2. 底層結構

在LinedList裏面有一個爲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;
        }
    }

3. 常用方法源碼解析

1. 構造方法

有兩個構造方法,一個是無參構造,一個是傳入一個Collection集合,注意該集合的泛型得是當前定義的泛型的子類!

    public LinkedList() {
    }
	//該構造函數將傳入的集合(符合要求的)轉化爲LinkedList
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

注意第二個構造函數:

  • 當傳入的集合泛型不符合條件時,則會編譯出錯;

2. 新增操作

即爲雙向鏈表,所以新增節點的操作可以爲頭插,也可以是尾插;

在LikedList裏面,定義了兩個節點:

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

這兩個初始值都爲null;

1. add()方法(尾插)

add方法默認是從尾部開始增加節點的:

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    void linkLast(E e) {
        //先獲取尾節點
        final Node<E> l = last;
        //這裏通過構造方法就將newNode的prev設置爲last,next設置爲空
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //判斷是不是空鏈表(插入前)
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        //記錄更改
        modCount++;
    }
2. addFirst()方法 (頭插)
    public void addFirst(E e) {
        linkFirst(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++;
    }

頭插爲尾插很相似,這裏就沒有加註釋!

3. 刪除操作

同樣的,刪除也分爲頭部刪除和尾部刪除

1. remove()方法 (也就是removeFirst方法)

remove方法實現的就是刪除頭節點,而具體實現查看源碼可以發現就是調用了removeFirst:

    public E remove() {
        return removeFirst();
    }
    
    public E removeFirst() {
        //獲取頭節點
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    private E unlinkFirst(Node<E> f) {
        // 保存要刪除的頭節點的值
        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置空,因爲一個節點的時候first和last都是這個節點;
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
2. removeLast()方法
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 = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

可以看出和removeFirst的邏輯是一樣的;

3. remove(int index)方法(指定下標刪除節點)⭐
    public E remove(int index) {
        //檢查下標是否合法的一系列操作
        checkElementIndex(index);
        return unlink(node(index));
    }
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
	//根據下標查找到這個節點並且返回;
    Node<E> node(int index) {
        //如果index小於size的一半,說明這個節點在雙向鏈表的左半側,從first向右開始查找;
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            //此時index大於size的一半,說明這個節點在雙向鏈表的右半側,從last向左開始查找;
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return 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;
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            //如果要刪除的節點後面爲空的話,則該節點刪除後,就應該把該節點前面的節點設置爲last;
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

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

注意點:

  • 可以看出刪除指定下標的節點的邏輯是:
    • 先通過 checkElementIndex(int index) 方法檢查要刪除的下標是否存在;
    • 然後通過node(int index) 方法找到要刪除的下標那個節點;
    • 執行unlink(Node node) 方法進行節點刪除;
      • 結合前面的來看:
        unlink代表刪除指定節點,unlinkFirst代表刪除頭節點,unlinkLast代表刪除尾節點!
  • 設計亮點:
    • node(int index)方法在查找節點的時候並不是從頭開始遍歷,而是通過了一次二分法,來確定是從頭還是從尾開始遍歷;

4. 節點查詢

1. get(int index)方法:獲取指定下標的節點值
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

上面已經分析過node這個方法,用來獲取指定下標的節點,所以上面查詢很簡單;

2. indexOf(Object o)方法 :獲取指定節點的值的下標
    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;
    }
3. contains(Object o)方法:是否存在某個節點值的節點⭐
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

這裏直接調用了上面的方法,很巧妙!!!

4. 迭代器

1. 單項迭代器

我們知道,在JDK1.5之前Iterable接口(注意不是Iterator接口,在Iterable接口裏定義了獲取Iterator的抽象方法)中的iterator()方法是直接在Collection接口中定義的,之後Collection接口就直接實現了Iterable接口 ,自然也就繼承了iterator這個抽象方法,從而讓自己的子類去完成實現 ,所以最原始的迭代器在每個集合裏都可以直接調用iterator()方法去獲取它的迭代器

LinkedList也一樣,只不過注意iterator的具體實現不是在LinkedList裏面完成的,而是它的父抽象類:AbstractSequentialList < E > ,而這個類又繼承了AbstractList 類,具體的實現就是在這個類裏面實現了迭代器的具體細節;

給上集合的一張類圖繼承關係:
在這裏插入圖片描述
所以LinkedList使用迭代器操作很簡單:

        LinkedList<Integer> list = new LinkedList<>();
        Iterator<Integer> integerIterator =  list.iterator();
        while (integerIterator.hasNext()) {
            System.out.print(integerIterator.next()+" ");
        }

2. 雙向迭代器 ⭐

在LinkedList中雖然沒有直接實現單向迭代器,但實現了雙向迭代器ListItr:
這個類是LinkedList的內部類,實現了ListIterator接口,而ListIterator接口實現了Iterator;
(ListIterator是專爲雙向迭代設計的一個接口,就是比Iterator多了關於從後往前遍歷的抽象方法

    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        //這裏得參數代表迭代的位置從哪開始,如果不填參數,則會調用父類AbstractList的迭代器
        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中提供了獲取這個迭代器的方法:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

3. 迭代器源碼分析⭐⭐⭐

1. 使用案例
        LinkedList<Integer> list = new LinkedList<>();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);

        //使用雙向迭代器從後往前迭代,這裏參數得填,否則默認爲零,那就從0號下標往前迭代,當然沒元素;
        ListIterator<Integer> listIterator =  list.listIterator(2);
        while (listIterator.hasPrevious()) {
            System.out.print(listIterator.previous()+" ");
        }
        System.out.println();
        //使用雙向迭代器從前往後迭代,這裏也可以傳參數,不傳默認爲零
        ListIterator<Integer> listIterator1 =  list.listIterator();
        while (listIterator1.hasNext()) {
            System.out.print(listIterator1.next()+" ");
        }

輸出:
2 1
1 2 3

我們在LinkedList類中看到了自己實現的雙向迭代器類ListItr這個類只有一個有參構造,需要傳入一個整數,代表開始迭代的位置,那麼上面怎麼能調用無參的listIterator()方法呢?

原因是無參的那個方法不是LinkedList實現的是他的父類AbstractList實現的,我們來看看這個類:
在這裏插入圖片描述
我們的ArrayList和LinkedList都繼承了這個抽象類,看它的類結構,裏面封裝了兩個內部類:Itr和ListItr,看着兩個名字就知道他們是用來實現迭代器的,而ListItr又繼承了Itr,看這兩個類中的方法:

  • Itr類中實現了基本的迭代,這個類實現了Iterator接口,沒有雙向迭代的功能;
    • 所謂基本迭代,進去發現其實就是默認位置從下標0開始,一次往後遍歷來實現遍歷元素;
  • ListItr類繼承了Itr,所以它也擁有基本的迭代功能,它也實現了ListIterator接口,所以它能實現雙向迭代功能,你看它的那幾個方法嘛,比如previous()和hasPrevious();
    • ListItr構造方法也是必須要傳入參數的,用來指定你從哪開始迭代,因爲有了從後往前迭代,所以你得指定你從哪個下標開始嘛;

而在AbstractList類中,提供了iterator()和listIterator(int index)和listIterator(無參)這些方法來獲取迭代器:

  • iterator()獲取的就是上面的Itr這個類的對象;

        public Iterator<E> iterator() {
            return new Itr();
        }
    
    • 我們在LinkedList的源碼中看到沒有覆寫iterator()這個方法,所以LinkedList獲取普通迭代器就是使用的父類Abstract類中的iterator方法來獲取的,而LinkedList實現了自己的雙向迭代器;
    • 注意:ArrayList中自己即實現了自己的單向迭代器(iterator()方法),也自己實現了listIractor方法來提供自己的雙向迭代器,所以ArrayList在使用迭代器的時候使用的是自己的,不是它的父類Abstract中的;
  • listIterator(int index)獲取的就是上面ListItr類的對象:

        public ListIterator<E> listIterator(final int index) {
            rangeCheckForAdd(index);
            //上面說過,這個雙向的迭代器必須傳入參數
            return new ListItr(index);
        }
    
  • 而listIterator(無參)就是默認參數爲零:

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

    這個方法正是上面那個問題的解!!LinkedList在使用listIterator卻不帶參數時,正是調用它的父類中的這個方法;

在這裏插入圖片描述

4. 總結⭐⭐

上面敘述得稍些雜亂,這裏記住這幾點:

  • LinkedList只實現了自己的有參雙向迭代器(listIterator(int index)方法獲取),若要使用普通迭代器和有參雙向迭代器,都是調用父類AbstractList類的;
    • 即LinkedList中只有listIterator(int index)這個方法;
  • ArrayList都實現了,都是調用自己的;
    • 即ArrayList中,iterator(),listIterator()、listIterator(int index)這三個方法它都有;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章