成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

趙連江 / 999人閱讀

摘要:迭代器迭代器迭代中表被修改考慮以下這段代碼在迭代器創(chuàng)建之后,對表進行了修改。這樣設(shè)計是因為,迭代器代表表中某個元素的位置,內(nèi)部會存儲某些能夠代表該位置的信息。

鏈表是對上一篇博文所說的順序表的一種實現(xiàn)。

與ArrayList思路截然不同,鏈表的實現(xiàn)思路是:

不同元素實際上是存儲在離散的內(nèi)存空間中的。

每一個元素都有一個指針指向下一個元素,這樣整個離散的空間就被“串”成了一個有順序的表。

從鏈表的概念來講,它可以算是一種遞歸的數(shù)據(jù)結(jié)構(gòu),因為鏈表拿掉第一個元素剩下的部分,依然構(gòu)成一個鏈表。

時間空間復雜度

通過索引定位其中的一個元素。由于不能像ArrayList那樣直接通過計算內(nèi)存地址偏移量來定位元素,只能從第一個元素開始順藤摸瓜來數(shù),因此為O(n)。

插入元素。實際上插入元素需要看情況:

如果指定鏈表中某個元素將其插之其后,那么首先得找出該元素對應的節(jié)點,還是O(n)。

如果能夠直接指定節(jié)點往其后插入(如通過迭代器),那么僅僅需要移動指針即可完成,O(1)。

移除元素。移除和插入類似,得看提供的參數(shù)是什么。如果提供的是元素所在的節(jié)點,那么也只需要O(1)。

LinkedList的繼承結(jié)構(gòu)

首先繼承結(jié)構(gòu)和ArrayList類似,實現(xiàn)了List接口。
但是,它繼承的是繼承了AbstractList類的AbstractSequentialList類,
這個類的作用也是給List中的部分函數(shù)提供默認實現(xiàn),只是這個類對鏈表這種List的實現(xiàn)提供了更多貼合的默認函數(shù)實現(xiàn)。

還有可以注意到,LinkedList實現(xiàn)了Deque接口,這也很顯然,鏈表這種結(jié)構(gòu)天然就適合當做雙端隊列使用。

LinkedList源碼分析 節(jié)點定義

先來看鏈表的節(jié)點定義:

    private static class Node {
        E item;
        Node next;
        Node prev;

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

可以看到,鏈表節(jié)點除了保存數(shù)據(jù)外,還需要保存指向前后節(jié)點的指針。
這里,鏈表即有后繼指針也有前驅(qū)指針,因此這是一個雙向鏈表。

一組節(jié)點之間按順序用指針指起來,就形成了鏈表的鏈狀結(jié)構(gòu)。

屬性和構(gòu)造函數(shù)
    transient int size = 0;
    transient Node first;
    transient Node last;

三個屬性,first和last分別指向鏈條的首節(jié)點和尾節(jié)點。
這樣有個好處,就是鏈表即可以使用頭插法也可以采用尾插法。

size屬性跟蹤了鏈表的元素個數(shù)。雖然說遍歷一遍鏈表也能統(tǒng)計到元素個數(shù),
但是那是O(n)的費時操作。
因此,我們可以發(fā)現(xiàn)鏈表的size方法是O(1)的時間復雜度。

    public LinkedList() {
    }

LinkedList的代碼很簡單,構(gòu)造函數(shù)空空如也。
空表中,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 x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

    Node node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

get和set的思路都是先根據(jù)索引定位到鏈表節(jié)點,然后獲得或設(shè)置節(jié)點中的數(shù)據(jù),這抽象出了node函數(shù),根據(jù)索引找到鏈表節(jié)點。

node的思路也很顯然,遍歷一遍即可得到。
這里做了一點優(yōu)化,我們可以發(fā)現(xiàn)LinkedList的實現(xiàn)是一個雙向鏈表,并且LinkedList持有了頭尾指針。
那么,根據(jù)索引和size就可以知道該節(jié)點是在鏈表的前半部分還是后半部分,
從而決定從頭節(jié)點開始遍歷還是從尾節(jié)點開始遍歷,這樣最多遍歷 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 l = last;
        final Node newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    void linkBefore(E e, Node succ) {
        final Node pred = succ.prev;
        final Node newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

添加/刪除的思路都類似,刪除的代碼就不貼了。如果能夠提供需要被操作的節(jié)點,就能直接移動下指針,O(1)完成。否則就需要遍歷找到這個節(jié)點再操作。
需要關(guān)注兩點:

有的操作是操作頭指針,有的操作是操作尾指針。但是不管操作哪一個,都需要維護另外一個指針及size的值。

如果是刪除,刪除后及時把相關(guān)節(jié)點的item字段置為null,以幫助gc能更快的釋放內(nèi)存。

modCount字段分析

之前閱讀ArrayList的代碼時發(fā)現(xiàn)了modCount這一字段,它是定義在AbstractList類中的。之前不知道它起到什么作用,這次給弄明白了。

迭代器 迭代器迭代中表被修改

考慮以下這段代碼:

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

在迭代器創(chuàng)建之后,對表進行了修改。這時候如果操作迭代器,則會得到異常java.util.ConcurrentModificationException
這樣設(shè)計是因為,迭代器代表表中某個元素的位置,內(nèi)部會存儲某些能夠代表該位置的信息。當表發(fā)生改變時,該信息的含義可能會發(fā)生變化,這時操作迭代器就可能會造成不可預料的事情。
因此,果斷拋異常阻止,是最好的方法。

實際上,這種迭代器迭代過程中表結(jié)構(gòu)發(fā)生改變的情況,更經(jīng)常發(fā)生在多線程的環(huán)境中。

記錄表被修改的標記

這種機制的實現(xiàn)就需要記錄表被修改,那么思路是使用狀態(tài)字段modCount
每當會修改表的操作執(zhí)行時,都將此字段加1。使用者只需要前后對比該字段就知道中間這段時間表是否被修改。

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

    private void linkFirst(E e) {
        final Node f = first;
        final Node 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 l = last;
        final Node newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
迭代器

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

    private class ListItr implements ListIterator {
        private Node lastReturned;
        private Node 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;

然后與現(xiàn)在的值對比判斷是否被修改:

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

這是一個內(nèi)部類,隱式持有LinkedList的引用,能夠直接訪問到LinkedList中的modCount字段。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71569.html

相關(guān)文章

  • LinkedList源碼解析

    摘要:我們來看相關(guān)源碼我們看到封裝的和操作其實就是對頭結(jié)點的操作。迭代器通過指針,能指向下一個節(jié)點,無需做額外的遍歷,速度非??臁2煌谋闅v性能差距極大,推薦使用迭代器進行遍歷。LinkedList類介紹 上一篇文章我們介紹了JDK中ArrayList的實現(xiàn),ArrayList底層結(jié)構(gòu)是一個Object[]數(shù)組,通過拷貝,復制等一系列封裝的操作,將數(shù)組封裝為一個幾乎是無限的容器。今天我們來介紹JD...

    番茄西紅柿 評論0 收藏0
  • LinkedList源碼解析

    摘要:我們來看相關(guān)源碼我們看到封裝的和操作其實就是對頭結(jié)點的操作。迭代器通過指針,能指向下一個節(jié)點,無需做額外的遍歷,速度非???。不同的遍歷性能差距極大,推薦使用迭代器進行遍歷。LinkedList類介紹 上一篇文章我們介紹了JDK中ArrayList的實現(xiàn),ArrayList底層結(jié)構(gòu)是一個Object[]數(shù)組,通過拷貝,復制等一系列封裝的操作,將數(shù)組封裝為一個幾乎是無限的容器。今天我們來介紹JD...

    番茄西紅柿 評論0 收藏0
  • LinkedList源碼分析:JDK源碼分析系列

    摘要:介紹是線程不安全的,允許元素為的雙向鏈表。構(gòu)造方法共有兩個構(gòu)造方法,一個是創(chuàng)建一個空的構(gòu)造函數(shù),一個是將已有的添加到中。是將元素插入到的頭部。下一篇文章繼續(xù)分析上次分析了的結(jié)構(gòu)和添加方法這次開始分析下面的。注意源碼版本為直接進入正題。 如果本文中有不正確的地方請指出由于沒有留言可以在公眾號添加我的好友共同討論。 1.介紹 LinkedList 是線程不安全的,允許元素為null的雙向鏈...

    blair 評論0 收藏0

發(fā)表評論

0條評論

趙連江

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<