LinkedList和ArrayList同樣是集合List的實現類,雖然較之ArrayList,其使用場景並很少,但一樣有用到的時候,那麼接下來,咱們來認識一下它。html
public static void main(String[] args) { List<String> stringList = new LinkedList<>(); List<String> tempList = new ArrayList<>(); tempList.add("牛魔王"); tempList.add("蛟魔王"); tempList.add("鵬魔王"); tempList.add("獅駝王"); tempList.add("獼猴王"); tempList.add("禺賊王"); tempList.add("美猴王"); List<String> stringList2 = new LinkedList<>(tempList); }
上面代碼中採用了兩種方式來定義LinkedList,能夠定義一個空集合,也能夠傳遞已有的集合,將其轉化爲LinkedList。咱們看一下源碼java
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ 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; /** * Constructs an empty list. */ public LinkedList() { } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public LinkedList(Collection<? extends E> c) { this(); addAll(c); } }
LinkedList繼承了AbstractSequentialList類,實現了List接口,AbstractSequentialList中已經實現了不少方法,如get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index),這些方法是咱們集合操做時使用最多的,不過這些方法在LinkedList中都已經被重寫了,而抽象方法在LinkedList中有了具體實現。所以咱們回到LinkedList類node
LinkedList類中定義了三個變量數組
size:集合的長度工具
first:雙向鏈表頭部節點性能
last:雙向鏈表尾部節點this
針對first變量和last變量,咱們看到是Node類的實體,這是一個靜態內部類,關於靜態內部類的講解,咱們在static五大應用場景一章已經有說明code
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是經過雙向鏈表實現的,而雙向鏈表就是經過Node類來體現的,類中經過item變量保存了當前節點的值,經過next變量指向下一個節點,經過prev變量指向上一個節點。orm
咱們知道隨機讀取元素不是LinkedList所擅長的,讀取效率比起ArrayList也低得多,那麼我來看一下爲何htm
public E get(int index) { checkElementIndex(index); return node(index).item; } /** * 返回一個指定索引的非空節點. */ Node<E> node(int index) { // assert isElementIndex(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; } }
從上述代碼中咱們能夠看到get(int index)方法是經過node(int index)來實現的,它的實現機制是:
比較傳入的索引參數index與集合長度size/2,若是是index小,那麼從第一個順序循環,直到找到爲止;若是index大,那麼從最後一個倒序循環,直到找到爲止。也就是說越靠近中間的元素,調用get(int index方法遍歷的次數越多,效率也就越低,並且隨着集合的愈來愈大,get(int index)執行性能也會指數級下降。所以在使用LinkedList的時候,咱們不建議使用這種方式讀取數據,能夠使用getFirst(),getLast()方法,將直接用到類中的first和last變量。
你們都在說LinkedList插入、刪除操做效率比較高,以stringList.add(「豬八戒」)爲例來看到底發生了什麼?
在LinkedList中咱們找到add(E e)方法的源碼
public boolean add(E e) { linkLast(e); return true; } /** * 設置元素e爲最後一個元素 */ 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:假如stringList爲空,那麼添加進來的node就是first,也是last,這個node的prev和next都爲null;
狀況2:假如stringList不爲空,那麼添加進來的node就是last,node的prev指向之前的最後一個元素,node的next爲null;同時之前的最後一個元素的next.
而若是經過stringList.add(1, 「豬八戒」)這種方式將元素添加到集合中呢?
//在指定位置添加一個元素 public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } /** * 在一個非空節點前插入一個元素 */ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
其實從代碼中看到和add(E e)的代碼實現沒有本質區別,都是經過新建一個Node實體,同時指定其prev和next來實現,不一樣點在於須要調用node(int index)經過傳入的index來定位到要插入的位置,這個也是比較耗時的,參考上面的get(int index)方法。
其實看到這裏,你們也都明白了。
LinkedList插入效率高是相對的,由於它省去了ArrayList插入數據可能的數組擴容和數據元素移動時所形成的開銷,但數據擴容和數據元素移動卻並非時時刻刻都在發生的。
這裏removeFirst()和removeLast()就很少說了,會用到類中定義的first和last變量,很是簡單,咱們看一下remove(Object o) 和 remove(int index)源碼
//刪除某個對象 public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; } //刪除某個位置的元素 public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } //刪除某節點,並將該節點的上一個節點(若是有)和下一個節點(若是有)關聯起來 E unlink(Node<E> x) { final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
其實實現都很是簡單,先找到要刪除的節點,remove(Object o)方法遍歷整個集合,經過 == 或 equals方法進行判斷;remove(int index)經過node(index)方法。
咱們主要列舉一下三種經常使用的遍歷方式,
普通for循環,加強for循環,Iterator迭代器
public static void main(String[] args) { LinkedList<Integer> list = getLinkedList(); //經過快速隨機訪問遍歷LinkedList listByNormalFor(list); //經過加強for循環遍歷LinkedList listByStrengThenFor(list); //經過快迭代器遍歷LinkedList listByIterator(list); } /** * 構建一個LinkedList集合,包含元素50000個 * @return */ private static LinkedList<Integer> getLinkedList() { LinkedList list = new LinkedList(); for (int i = 0; i < 50000; i++){ list.add(i); } return list; } /** * 經過快速隨機訪問遍歷LinkedList */ private static void listByNormalFor(LinkedList<Integer> list) { // 記錄開始時間 long start = System.currentTimeMillis(); int size = list.size(); for (int i = 0; i < size; i++) { list.get(i); } // 記錄用時 long interval = System.currentTimeMillis() - start; System.out.println("listByNormalFor:" + interval + " ms"); } /** * 經過加強for循環遍歷LinkedList * @param list */ public static void listByStrengThenFor(LinkedList<Integer> list){ // 記錄開始時間 long start = System.currentTimeMillis(); for (Integer i : list) { } // 記錄用時 long interval = System.currentTimeMillis() - start; System.out.println("listByStrengThenFor:" + interval + " ms"); } /** * 經過快迭代器遍歷LinkedList */ private static void listByIterator(LinkedList<Integer> list) { // 記錄開始時間 long start = System.currentTimeMillis(); for(Iterator iter = list.iterator(); iter.hasNext();) { iter.next(); } // 記錄用時 long interval = System.currentTimeMillis() - start; System.out.println("listByIterator:" + interval + " ms"); }
執行結果以下:
listByNormalFor:1067 ms listByStrengThenFor:3 ms listByIterator:2 ms
經過普通for循環隨機訪問的方式執行時間遠遠大於迭代器訪問方式,這個咱們能夠理解,在前面的get(int index)方法中已經有過說明,那麼爲何加強for循環能作到迭代器遍歷差很少的效率?
經過反編譯工具後獲得以下代碼
public static void listByStrengThenFor(LinkedList<Integer> list) { long start = System.currentTimeMillis(); Integer localInteger; for (Iterator localIterator = list.iterator(); localIterator.hasNext(); localInteger = (Integer)localIterator.next()) {} long interval = System.currentTimeMillis() - start; System.out.println("listByStrengThenFor:" + interval + " ms"); }
很明顯了,加強for循環遍歷時也調用了迭代器Iterator,不過多了一個賦值的過程。
還有相似於pollFirst(),pollLast()取值後刪除的方法也能達到部分的遍歷效果。
本文基於java8從定義一個LinkList入手,逐步展開,從源碼角度分析LinkedList雙向鏈表的結構是如何構建的,同時針對其經常使用方法進行分析,包括get,add,remove以及經常使用的遍歷方法,並簡單的說明了它的插入、刪除操做爲什麼相對高效,而取值操做性能相對較低,如有不對之處,請批評指正,望共同進步,謝謝!