程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java進階之----LinkedList源碼分析

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

編輯:JAVA綜合教程

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


今天在看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 c) {
        this();
        addAll(c);
    }

    public boolean addAll(Collection c) {
        return addAll(size, c);
    }

    public boolean addAll(int index, Collection c) {
    	// 檢查傳入的索引值是否在合理范圍內
        checkPositionIndex(index);
        // 將給定的Collection對象轉為Object數組
        Object[] a = c.toArray();
        int numNew = a.length;
        // 數組為空的話,直接返回false
        if (numNew == 0)
            return false;
        // 數組不為空
        Node 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 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 item;
        Node next;
        Node prev;

        Node(Node prev, E element, Node 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 f = first;
        final Node newNode = new Node<>(null, e, f); // 創建新的節點,新節點的後繼指向原來的頭節點,即將原頭節點向後移一位,新節點代替頭結點的位置。
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }
其實只要理解了上邊的數據結構,這段代碼是很好理解的。 加入一個新的節點,看方法名就能知道,是在現在的鏈表的頭部加一個節點,既然是頭結點,那麼頭結點的前繼必然為null,所以這也是Node newNode = new Node<>(null, e, f);這樣寫的原因。 之後將first指向了當前鏈表的頭結點,之後對之前的頭節點進行了判斷,若在插入元素之前頭結點為null,則當前加入的元素就是第一個幾點,也就是頭結點,所以當前的狀況就是:頭結點=剛剛加入的節點=尾節點。若在插入元素之前頭結點不為null,則證明之前的鏈表是有值的,那麼我們只需要把新加入的節點的後繼指向原來的頭結點,而尾節點則沒有發生變化。這樣一來,原來的頭結點就變成了第二個節點了。達到了我們的目的。
addLast方法在實現上是個addFirst是一致的,這裡就不在贅述了。有興趣的朋友可以看看源代碼。 其實,LinkedList中add系列的方法都是大同小異的,都是創建新的節點,改變之前的節點的指向關系。僅此而已。  

getFirst/getLast方法分析

    public E getFirst() {
        final Node f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    public E getLast() {
        final Node 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 node(int 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;
        }
    }
一開始我很費解,這是要干嘛?後來我才明白,代碼要做的是:判斷給定的索引值,若索引值大於整個鏈表長度的一半,則從後往前找,若索引值小於整個鏈表的長度的一般,則從前往後找。這樣就可以保證,不管鏈表長度有多大,搜索的時候最多只搜索鏈表長度的一半就可以找到,大大提升了效率。

removeFirst/removeLast方法分析

    public E removeFirst() {
        final Node f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    private E unlinkFirst(Node f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node 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的其他方法,大致上都是包裝了以上這幾個方法,沒有什麼其他的大的變動。
     

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved