JDK1.7-LinkedList循環鏈表優化


 最近在看jdk1.7的時候,發現LinkedList 和1.6中的變化。

首先,簡單介紹一下LinkedList:

LinkedList是List接口的雙向鏈表實現。由於是鏈表結構,所以長度沒有限制;而且添加/刪除元素的時候,只需要改變指針的指向(把鏈表斷開,插入/刪除元素,再把鏈表連起來)即可,非常方便,而ArrayList卻需要重整數組 (add/remove中間元素)。所以LinkedList適合用於添加/刪除操作頻繁的情況。

 

--------------------------------the code --------------------------------

在JDK 1.7之前(此處使用JDK1.6來舉例),LinkedList是通過headerEntry實現的一個循環鏈表的。先初始化一個空的Entry,用來做header,然后首尾相連,形成一個循環鏈表:

在LinkedList中提供了兩個基本屬性size、header。

private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;

 其中size表示的LinkedList的大小,header表示鏈表的表頭,Entry為節點對象。

private static class Entry<E> {
        E element;        //元素節點
        Entry<E> next;    //下一個元素
        Entry<E> previous;  //上一個元素

        Entry(E element, Entry<E> next, Entry<E> previous) {
            this.element = element;
            this.next = next;
            this.previous = previous;
        }
    }

  上面為Entry對象的源代碼,Entry為LinkedList的內部類,它定義了存儲的元素。該元素的前一個元素、后一個元素,這是典型的雙向鏈表定義方式。

 

 

        每次添加/刪除元素都是默認在鏈尾操作。對應此處,就是在header前面操作,因為遍歷是next方向的,所以在header前面操作,就相當於在鏈表尾操作。

如下面的插入操作addBefore以及圖示,如果插入obj_3,只需要修改header.previous和obj_2.next指向obj_3即可。

private Entry<E> addBefore(E e, Entry<E> entry) {
        //利用Entry構造函數構建一個新節點 newEntry,
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
        //修改newEntry的前后節點的引用,確保其鏈表的引用關系是正確的
        newEntry.previous.next = newEntry;
        newEntry.next.previous = newEntry;
        //容量+1
        size++;
        //修改次數+1
        modCount++;
        return newEntry;
    }

 在addBefore方法中無非就是做了這件事:構建一個新節點newEntry,然后修改其前后的引用。

 

 

---------------------------------------------

     在JDK 1.7,1.6的headerEntry循環鏈表被替換成了first和last組成的非循環鏈表。

transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

 

在初始化的時候,不用去new一個Entry。

/**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

 

 

         在插入/刪除的時候,也是默認在鏈尾操作。把插入的obj當成newLast,掛在oldLast的后面。另外還要先判斷first是否為空,如果為空則first = obj。

如下面的插入方法linkLast,在尾部操作,只需要把obj_3.next指向obj_4即可。

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

 

其中:

 1 private static class Node<E> {
 2         E item;
 3         Node<E> next;
 4         Node<E> prev;
 5 
 6         Node(Node<E> prev, E element, Node<E> next) {
 7             this.item = element;
 8             this.next = next;
 9             this.prev = prev;
10         }
11     }

 

---------------------------------------------

 

To sum up: 【1.6-header循環鏈表】 V.S 【1.7-first/last非循環鏈表】

      JDK 1.7中的first/last對比以前的header有下面幾個好處:

1、  first / last有更清晰的鏈頭、鏈尾概念,代碼看起來更容易明白。

2、  first / last方式能節省new一個headerEntry。(實例化headerEntry是為了讓后面的方法更加統一,否則會多很多header的空校驗)

3、  在鏈頭/尾進行插入/刪除操作,first /last方式更加快捷。

【插入/刪除操作按照位置,分為兩種情況:中間 和 兩頭。

        在中間插入/刪除,兩者都是一樣,先遍歷找到index,然后修改鏈表index處兩頭的指針。

        在兩頭,對於循環鏈表來說,由於首尾相連,還是需要處理兩頭的指針。而非循環鏈表只需要處理一邊first.previous/last.next,所以理論上非循環鏈表更高效。恰恰在兩頭(鏈頭/鏈尾) 操作是最普遍的】

(對於遍歷來說,兩者都是鏈表指針循環,所以遍歷效率是一樣的。)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM