源碼分析 (二) LinkedList JDK 1.8 源碼分析

簡介

本文基於JDK1.8中LinkedList源碼分析java

類定義node

LinkedList繼承

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

由上圖以及類定義片斷可知,LinkedList繼承了AbstractSequentialList而且實現List,Deque,Cloneable, Serializable接口。 其中,AbstractSequentialList相較於AbstractList(ArrayList的父類),只支持次序訪問,而不支持隨機訪問,由於它的 get(int index) ,set(int index, E element), add(int index, E element), remove(int index) 都是基於迭代器實現的。因此在LinkedList使用迭代器遍歷更快,而ArrayList使用get (i)更快。 接口方面,LinkedList多繼承了一個Deque接口,因此實現了雙端隊列的一系列方法。數組

####基本數據結構bash

transient int size = 0;
transient Node<E> first;
transient Node<E> last;
複製代碼
複製代碼

LinkedList中主要定義了頭節點指針,尾節點指針,以及size用於計數鏈表中節點個數。那麼每個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節點

  • getFirst獲取頭節點
  • getLast獲取尾節點
  • get(int index) 獲取指定位置的節點
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
複製代碼
複製代碼

檢查非空後,直接返回first節點的item源碼分析

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

檢查非空後,直接返回last節點的itemui

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
複製代碼
複製代碼

首先檢查index範圍,而後調用node(index)獲取index處的節點,返回該節點的item值。 看看node(index)的實現,後面不少地方藉助於這個小函數:this

Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {  //判斷index是在鏈表偏左側仍是偏右側
            Node<E> x = first;
            for (int i = 0; i < index; i++)  //從左邊往右next
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)  //從右往左prev
                x = x.prev;
            return x;
        }
    }
複製代碼
複製代碼

由上面能夠看出,在鏈表中找一個位置,只能經過不斷遍歷。spa

另外還有IndexOf,LastIndexOf操做,找出指定元素在LinkedList中的位置: 也是一個從前找,一個從後找,只分析下IndexOf操做:

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;
    }
複製代碼
複製代碼

主要也是不斷遍歷,找到值相等的節點,返回它的Index。

####更改節點的值 主要也就是一個set函數

public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
複製代碼
複製代碼

根據index找到指定節點,更改它的值,而且會返回原有值。

####插入節點 LinkedList實現了Deque接口,支持在鏈表頭部和尾部插入元素:

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

public void addLast(E e) {
        linkLast(e);
    }
複製代碼
複製代碼

這裏咱們能夠看到內部實現的函數是linkFirst,linkLast, 鏈表頭插入元素:

private void linkFirst(E e) {
        final Node<E> f = first;  //現將原有的first節點保存在f中
        final Node<E> newNode = new Node<>(null, e, f); //將新增節點包裝成一個Node節點,同時該節點的next指向以前的first
        first = newNode;  //將新增的節點設成first
        if (f == null)
            last = newNode;   //若是原來的first就是空,那麼新增的newNode同時也是last節點
        else
            f.prev = newNode;  //若是不是空,則原來的first節點的前置節點就是如今新增的節點
        size++;  //插入元素後,節點個數加1
        modCount++;  //還記得上一篇講述的快速失敗嗎?這邊依然是這個做用
    }
複製代碼
複製代碼

在頭部插入

主要流程:

  1. 將原有頭節點保存到f
  2. 將插入元素包裝成新節點,而且該新節點的next指向原來的頭節點,即f
  3. 若是原來的頭節點f爲空的話,那麼新插的頭節點也是last節點,不然,還要設置f的前置節點爲NewNode,即NewNode如今是first節點了
  4. 記得增長size,記錄修改次數modCount

鏈表尾插入元素

void linkLast(E e) {
        final Node<E> l = last;   //將尾節點保存到l中
        final Node<E> newNode = new Node<>(l, e, null); //把e包裝成Node節點,同時把該節點的前置節點設置爲l
        last = newNode; //把新插的節點設置爲last節點
        if (l == null)
            first = newNode;  //若是原來的last節點爲空,那麼新增的節點在乎義上也是first節點
        else
            l.next = newNode; //不然的話還要將newNode設爲原有last節點的後繼節點,因此newNode如今是新的Last節點
        size++;
        modCount++;
    }
複製代碼
複製代碼

在尾部插入,詳細流程見註釋

在指定index處插入元素

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

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
複製代碼
複製代碼

首先調用checkPositionIndex檢查index值是否在範圍內,若是index在最後的話,就調用在尾部插入的函數,不然調用LinkBefore,主要看LinkBefore如何實現的:

void linkBefore(E e, Node<E> succ) {  //在succ節點前插入newNode
        // assert succ != null;
        final Node<E> pred = succ.prev;   //將succ的前置節點記爲pred
        final Node<E> newNode = new Node<>(pred, e, succ);   以pred爲前置節點,以succ爲後繼節點創建newNode
        succ.prev = newNode;   //將new Node設爲succ的前置節點
        if (pred == null)
            first = newNode;   //若是原有的succ的前置節點爲空,那麼新插入的newNode就是first節點
        else
            pred.next = newNode;  // 不然,要把newNode設爲原來pred節點的後置節點
        size++;
        modCount++;
    }
複製代碼
複製代碼

在指定index處插入元素,流程看代碼註釋

其他幾個經常使用的add方法也是基於以上函數:

public boolean add(E e) {
        linkLast(e);
        return true;
    }
複製代碼
複製代碼

add函數默認在函數尾部插入元素

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
複製代碼
複製代碼

addAll(Collection<? extends E> c)指的是在list尾部插入一個集合,具體實現又依賴於addAll(size,c),指的是在指定位置插入一個集合:

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);  // 檢查index位置是否超出範圍

        Object[] a = c.toArray();   //將集合c轉變成Object數組 同時計算數組長度
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {   //若是插入位置爲尾部,succ則爲null,原來鏈表的last設置爲此刻的pred節點
            succ = null;
            pred = last;
        } else {
            succ = node(index);   //不然,index所在節點設置爲succ,succ的前置節點設爲pred
            pred = succ.prev;
        }

        for (Object o : a) { //循環遍歷數組a
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);   //以a爲element元素構造Node節點
            if (pred == null)
                first = newNode;     //若是pred爲空,此Node就爲first節點
            else
                pred.next = newNode;   //不然就往pred後插入該Node
            pred = newNode;       //newNode此刻成爲新的pred, 這樣不斷循環遍歷,把這個數組插入到鏈表中
        }

        if (succ == null) { //若是succ爲空,就把插入的最後一個節點設爲last
            last = pred;
        } else {
            pred.next = succ;   //不然,把以前保存的succ接到pred後面
            succ.prev = pred;  //而且把succ的前向指針指向插入的最後一個元素
        }

        size += numNew;   //記錄增加的尺寸
        modCount++;  //記錄修改次數
        return true;
    }
複製代碼
複製代碼

具體流程能夠看代碼中的註釋。

####刪除節點#### 由於實現了Deque的接口,因此仍是實現了removeFirst, removeLast方法。

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

首先保存fisrt節點到f,若是爲空,拋出NoSuchElementException異常,實際仍是調用unlinkFirst完成操做。

private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;   //把f.item的值保存到element
        final Node<E> next = f.next;   //把f.next的值記住
        f.item = null;    
        f.next = null; // help GC   //把item和next的都指向null
        first = next;    //next成爲實際的first節點
        if (next == null)  //next爲空的話,由於next是第一個節點,因此鏈表都是空的,last也爲空
            last = null;
        else
            next.prev = null;  //next不爲空,也要將它的前驅節點記爲null,由於next是第一個節點
        size--;  //節點減小一個
        modCount++;  //操做次數加1
        return element;
    }
複製代碼
複製代碼

removeLast的實現基本和removeFirst對稱:

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

主要實現仍是藉助於unlinkLast:

private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;   //保存最後一個節點的值
        final Node<E> prev = l.prev;   //把最後一個節點的前驅節點記爲prev,它將成爲last節點
        l.item = null;
        l.prev = null; // help GC  //l節點的item和prev都記爲空
        last = prev;    //此時設置剛纔記錄的前驅節點爲last
        if (prev == null) //prev爲空的話,說明要刪除的l前面原來沒節點,那麼刪了l,整個鏈表爲空
            first = null;
        else
            prev.next = null;   //prev成爲最後一個節點,沒有後繼節點
        size--;
        modCount++;
        return element;
    }
複製代碼
複製代碼

在指定index刪除

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
複製代碼

複製代碼

首先也是檢查index是否合法,不然拋出IndexOutOfBoundsException異常。 若是刪除成功,返回刪除的節點。 具體實現依賴於unlink,也就是unlink作實際的刪除操做:

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;
    }
複製代碼

複製代碼

刪除一個節點

  1. 將刪除的節點保存在element裏,同時把要刪除節點的前驅節點標記爲prev,後繼節點標記爲next
  2. 若是prev爲空,那麼next節點直接爲first節點,反之把prevnext指向next節點,如圖中上面彎曲的紅色箭頭所示;
  3. 若是next爲空,那麼prev節點直接爲last節點,反之把nextprev指向prev節點,如圖中下面彎曲的藍色箭頭所示;
  4. 把要刪除的節點置空,返回第一步保存的element

還有種刪除是以刪除的元素做爲參數:

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;
    }
複製代碼

複製代碼
  • o爲null,也是遍歷鏈表,找到第一個值爲null的節點,刪除;
  • o部位空,遍歷鏈表,找到第一個值相等的節點,調用unlink(x)刪除。

####清空列表

public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        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++;
    }
複製代碼

複製代碼

由代碼看出,也就是循環遍歷整個鏈表,將每一個節點的每一個屬性都置爲空。

LinkedList還定義了不少別的方法,基本上和上面分析的幾個函數功能相似

  • elemet和GetFirst同樣,都返回列表的頭,而且移除它,若是列表爲空,都會拋出NoSucnElement異常;
  • peek也會返回第一個元素,可是爲空時返回null, 不拋異常;
  • remove方法內部就是調用removeFirst,因此表現相同,返回移除的元素,若是列表爲空,都會拋出NoSucnElement異常;
  • poll也是移除第一個元素,只是會在列表爲空時只返回null;
  • offer和offerLast在尾部add節點, 最終調用的都是addLast方法,offerFirst在頭保護add節點,調用的就是addFirst方法;
  • peekFirst返回頭節點,爲空時返回null,peekLast返回尾節點,爲空時返回null,都不會刪除節點;
  • pollFirst刪除並返回頭節點,爲空時返回null ,pollLast刪除並返回尾節點,爲空時返回null;
  • push和pop也是讓LinkedList具備棧的功能,也只是調用了addFirst和removeFirst函數。

####ListIterator 最後重點說一下LinkedList中如何實現了ListIterator迭代器。 ListIterator是一個更增強大的Iterator迭代器的子類型,它只能用於各類List類的訪問。儘管Iterator只能向前移動,可是ListIterator能夠雙向移動。它還能夠產生相對於迭代器在列表中指向的當前位置的前一個和後一個元素的索引,能夠用set()方法替換它訪問過得最後一個元素。

定義以下:

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

複製代碼

指定index,能夠在一開始就獲取一個指向index位置元素的迭代器。 實際上LinkedList是實現了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);   //返回index處的節點,記錄爲next
            nextIndex = index;  //記錄當前索引
        } 

        public boolean hasNext() {
            return nextIndex < size;   //經過判斷nextIndex是否還在size範圍內
        }

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

            lastReturned = next;   //記錄上一次的值
            next = next.next;  //日後移動一個節點
            nextIndex++; //索引值也加1
            return lastReturned.item;   //next會返回上一次的值
        }

        public boolean hasPrevious() {  //經過哦按段nextIndex是否還大於0,若是<=0,就證實沒有前驅節點了
            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();
        }
    }
複製代碼

複製代碼

咱們能夠發如今ListIterator的操做中仍然有checkForComodification函數,並且在上面敘述的各類操做中仍是會記錄modCount,因此LinkedList也是會產生快速失敗事件的。

相關文章
相關標籤/搜索