源碼|jdk源碼之LinkedList與modCount字段

鏈表是對上一篇博文所說的順序表的一種實現。java

與ArrayList思路大相徑庭,鏈表的實現思路是:node

  1. 不一樣元素其實是存儲在離散的內存空間中的。
  2. 每個元素都有一個指針指向下一個元素,這樣整個離散的空間就被「串」成了一個有順序的表。

從鏈表的概念來說,它能夠算是一種遞歸的數據結構,由於鏈表拿掉第一個元素剩下的部分,依然構成一個鏈表。數據結構

<!-- more -->多線程

時間空間複雜度

  1. 經過索引定位其中的一個元素。因爲不能像ArrayList那樣直接經過計算內存地址偏移量來定位元素,只能從第一個元素開始順藤摸瓜來數,所以爲O(n)。
  2. 插入元素。實際上插入元素須要看狀況:函數

    • 若是指定鏈表中某個元素將其插之其後,那麼首先得找出該元素對應的節點,仍是O(n)。
    • 若是可以直接指定節點往其後插入(如經過迭代器),那麼僅僅須要移動指針便可完成,O(1)。
  3. 移除元素。移除和插入相似,得看提供的參數是什麼。若是提供的是元素所在的節點,那麼也只須要O(1)。

LinkedList的繼承結構

img

首先繼承結構和ArrayList相似,實現了List接口。
可是,它繼承的是繼承了AbstractList類的AbstractSequentialList類,
這個類的做用也是給List中的部分函數提供默認實現,只是這個類對鏈表這種List的實現提供了更多貼合的默認函數實現。源碼分析

還有能夠注意到,LinkedList實現了Deque接口,這也很顯然,鏈表這種結構自然就適合當作雙端隊列使用。優化

LinkedList源碼分析

節點定義

先來看鏈表的節點定義:this

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

能夠看到,鏈表節點除了保存數據外,還須要保存指向先後節點的指針。
這裏,鏈表即有後繼指針也有前驅指針,所以這是一個雙向鏈表。spa

一組節點之間按順序用指針指起來,就造成了鏈表的鏈狀結構。線程

屬性和構造函數

transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;

三個屬性,first和last分別指向鏈條的首節點和尾節點。
這樣有個好處,就是鏈表便可以使用頭插法也能夠採用尾插法。

size屬性跟蹤了鏈表的元素個數。雖說遍歷一遍鏈表也能統計到元素個數,
可是那是O(n)的費時操做。
所以,咱們能夠發現鏈表的size方法是O(1)的時間複雜度。

public LinkedList() {
    }

LinkedList的代碼很簡單,構造函數空空如也。
空表中,first和last字段都爲null。

get和set方法

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

    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和set的思路都是先根據索引定位到鏈表節點,而後得到或設置節點中的數據,這抽象出了node函數,根據索引找到鏈表節點。

node的思路也很顯然,遍歷一遍便可獲得。
這裏作了一點優化,咱們能夠發現LinkedList的實現是一個雙向鏈表,而且LinkedList持有了頭尾指針。
那麼,根據索引和size就能夠知道該節點是在鏈表的前半部分仍是後半部分,
從而決定從頭節點開始遍歷仍是從尾節點開始遍歷,這樣最多遍歷 N / 2次便可找到。

添加/刪除

public boolean add(E e) {
        linkLast(e);
        return true;
    }

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

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

    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) {
        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++;
    }

添加/刪除的思路都相似,刪除的代碼就不貼了。若是可以提供須要被操做的節點,就能直接移動下指針,O(1)完成。不然就須要遍歷找到這個節點再操做。
須要關注兩點:

  1. 有的操做是操做頭指針,有的操做是操做尾指針。可是無論操做哪個,都須要維護另一個指針及size的值。
  2. 若是是刪除,刪除後及時把相關節點的item字段置爲null,以幫助gc能更快的釋放內存。

modCount字段分析

以前閱讀ArrayList的代碼時發現了modCount這一字段,它是定義在AbstractList類中的。以前不知道它起到什麼做用,此次給弄明白了。

迭代器

迭代器迭代中表被修改

考慮如下這段代碼:

List<Integer> list = new LinkedList<>();
    Iterator<Integer> it = list.listIterator();
    list.add(1);
    it.next();

在迭代器建立以後,對錶進行了修改。這時候若是操做迭代器,則會獲得異常java.util.ConcurrentModificationException
這樣設計是由於,迭代器表明表中某個元素的位置,內部會存儲某些可以表明該位置的信息。當表發生改變時,該信息的含義可能會發生變化,這時操做迭代器就可能會形成不可預料的事情。
所以,果斷拋異常阻止,是最好的方法。

實際上,這種迭代器迭代過程當中表結構發生改變的狀況,更常常發生在多線程的環境中。

記錄表被修改的標記

這種機制的實現就須要記錄表被修改,那麼思路是使用狀態字段modCount
每當會修改表的操做執行時,都將此字段加1。使用者只須要先後對比該字段就知道中間這段時間表是否被修改。

如linkedList中的頭插和尾插函數,就將modCount字段自增:

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

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

迭代器

迭代器使用該字段來判斷,

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 void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

迭代器開始時記錄下初始的值:

private int expectedModCount = modCount;

而後與如今的值對比判斷是否被修改:

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

這是一個內部類,隱式持有LinkedList的引用,可以直接訪問到LinkedList中的modCount字段。

相關文章
相關標籤/搜索