Java進階之----LinkedList源碼分析__java

來源:互聯網
上載者:User

今天在看LinkedList的原始碼的時候,遇到了一個坑。我研究源碼時,發現LinkedList是一個直線型的鏈表結構,但是我在baidu搜尋資料的時候,關於這部分的源碼解析,全部都說LinkedList是一個環形鏈表結構。。我糾結了好長時間,還以為我理解錯了,最後還是在Google搜到了結果:因為我看的源碼是1.7的而baidu出來的幾乎全部都是1.6的。而且也沒有對應的說明。在1.7之後,oracle將LinkedList做了一些最佳化,將1.6中的環形結構最佳化為了直線型了鏈表結構。這裡要提示一下朋友們,看源碼的時候,一定要看版本,有的情況是屬於小改動,有的地方可能有大改動,這樣只會越看越迷糊。

好,言歸正傳。我們來分析一下Java中LinkedList的部分源碼。(本文針對的是1.7的源碼)


LinkedList的基本結構 之前我一直在說鏈錶鏈表,那什麼是鏈表。顧名思義,鏈表就和鏈子一樣,每一環都要串連著後邊的一環和前邊的一環,這樣,當我們需要找這根鏈子的某一環的時候,只要我們能找到鏈子的任意一環,都可以找到我們需要的那一環。我們看一個圖,就能很好的理解了。
在LinkedList中,我們把鏈子的“環”叫做“節點”,每個節點都是同樣的結構。節點與節點之間相連,構成了我們LinkedList的基本資料結構,也是LinkedList的核心。 我們再來看一下LinkedList在jdk1.6和1.7直接結構的區別 首先看1.7中的結構

再來看1.6中的結構
對比一下,知道區別在哪裡了吧。在1.7中,去掉了環形結構,自然在代碼中的也會有部分的改變。 理解了上邊的結構,在分析的時候就會容易許多。

LinkedList的構造方法 LinkedList包含3個全域參數, size存放當前鏈表有多少個節點。 first為指向鏈表的第一個節點的引用。 last為指向鏈表的最後一個節點的引用。
LinkedList構造方法有兩個,一個是無參構造,一個是傳入Collection對象的構造。

    // 什麼都沒做,是一個空實現    public LinkedList() {    }    public LinkedList(Collection<? extends E> c) {        this();        addAll(c);    }    public boolean addAll(Collection<? extends E> c) {        return addAll(size, c);    }    public boolean addAll(int index, Collection<? extends E> c) {    // 檢查傳入的索引值是否在合理範圍內        checkPositionIndex(index);        // 將給定的Collection對象轉為Object數組        Object[] a = c.toArray();        int numNew = a.length;        // 數組為空白的話,直接返回false        if (numNew == 0)            return false;        // 數組不為空白        Node<E> pred, succ;        if (index == size) {        // 構造方法調用的時候,index = size = 0,進入這個條件。            succ = null;            pred = last;        } else {        // 鏈表非空時調用,node方法返回給定索引位置的節點對象            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; // 將當前鏈表最後一個節點賦值給last        } else {        // 鏈表非空時,將斷開的部分串連上            pred.next = succ;            succ.prev = pred;        }        // 記錄當前節點個數        size += numNew;        modCount++;        return true;    }
這裡要說明一下,Node是LinkedList的內部私人類,它的組成很簡單,只有一個構造方法。
    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;        }    }
構造方法的參數順序是:前繼節點的引用,資料,後繼節點的引用。
有了上邊的說明,我們來看LinkedList的構造方法。
這段代碼還是很好理解的。我們可以配合圖片來深入理解。 這段代碼分為了2種情況,一個是原來的鏈表是空的,一個是原來的鏈表有值。我們分別來看 原來有值的情況



配合代碼來看,是不是思路清晰了許多。 原來鏈表是空的話就更好辦了,直接把傳入的Collection對象轉化為數組,數組的第一個值就作為頭結點,即head,之後的順序往裡加入即可。並且節省了改變原節點指向的的操作。
對與兩種構造方法,總結起來,可以概括為:無參構造為空白實現。有參構造傳入Collection對象,將對象轉為數組,並按遍曆順序將數組首尾相連,全域變數first和last分別指向這個鏈表的第一個和最後一個。
LinkedList部分方法分析 addFirst/addLast分析 我們來看代碼
    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++;    }
其實只要理解了上邊的資料結構,這段代碼是很好理解的。 加入一個新的節點,看方法名就能知道,是在現在的鏈表的頭部加一個節點,既然是頭結點,那麼頭結點的前繼必然為null,所以這也是Node<E> newNode = new Node<>(null, e, f);這樣寫的原因。 之後將first指向了當前鏈表的頭結點,之後對之前的前端節點進行了判斷,若在插入元素之前頭結點為null,則當前加入的元素就是第一個幾點,也就是頭結點,所以當前的狀況就是:頭結點=剛剛加入的節點=尾節點。若在插入元素之前頭結點不為null,則證明之前的鏈表是有值的,那麼我們只需要把新加入的節點的後繼指向原來的頭結點,而尾節點則沒有發生變化。這樣一來,原來的頭結點就變成了第二個節點了。達到了我們的目的。
addLast方法在實現上是個addFirst是一致的,這裡就不在贅述了。有興趣的朋友可以看看原始碼。
其實,LinkedList中add系列的方法都是大同小異的,都是建立新的節點,改變之前的節點的指向關係。僅此而已。
getFirst/getLast方法分析
    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;    }
這段代碼即不需要解析了吧。。很簡單的。
get方法分析 這裡主要看一下它調用的node方法
    public E get(int index) {    // 校正給定的索引值是否在合理範圍內        checkElementIndex(index);        return node(index).item;    }    Node<E> node(int 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;        }    }
一開始我很費解,這是要幹嘛。後來我才明白,代碼要做的是:判斷給定的索引值,若索引值大於整個鏈表長度的一半,則從後往前找,若索引值小於整個鏈表的長度的一般,則從前往後找。這樣就可以保證,不管鏈表長度有多大,搜尋的時候最多隻搜尋鏈表長度的一半就可以找到,大大提升了效率。
removeFirst/removeLast方法分析
    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;    }

摘迴轉結點,將原來的第二個節點變為頭結點,改變frist的指向,若之前僅剩一個節點,移除之後全部置為了null。
對於LinkedList的其他方法,大致上都是封裝了以上這幾個方法,沒有什麼其他的大的變動。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.