最近在看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,所以理論上非循環鏈表更高效。恰恰在兩頭(鏈頭/鏈尾) 操作是最普遍的】
(對於遍歷來說,兩者都是鏈表指針循環,所以遍歷效率是一樣的。)