LinkedList的UML圖:
LinkedList真正用來存儲元素的數據結構-> Node類
Node類是LinkedList中的私有內部類,LinkedList中經過Node來存儲集合中的元素node
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節點個數,用來記錄LinkedList的大小 transient int size = 0; /** * 指向第一個節點的指針。用來表示LinkedList的頭結點 * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * 指向最後一個節點的指針。用來表示LinkedList的尾結點 * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last;
ArrayList的構造函數有三個,比LinkedList多提供了一個設置初始化容量來初始化類。LinkedList沒有提供該方法,緣由:由於LinkedList底層是經過鏈表實現的,每添加新元素的時候,都是經過連接新的節點實現的,也就是說它的容量是隨着元素的個數的變化而動態變化的。而ArrayList底層是經過數組來存儲新添加的元素的,因此咱們能夠爲ArrayList設置初始容量(也就是設置數組的大小)數組
/** * 空參構造 */ public LinkedList() { } /** * 構造包含指定集合元素的列表,按照集合的迭代器返回元素的順序 * 傳入一個結合做爲參數初始化LinkedList。 * * @param c 將其元素放置在此列表中的集合 * @throws NullPointerException 若是指定的集合爲空 */ public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
在LinkedList帶集合參數的構造函數中有一個重要的方法addAll(int index, Collection),這塊兒的方法有點繞,若是沒看懂,建議手畫一遍。數據結構
/** * 經過調用addAll(int index, Collection<? extends E> c)添加 集合 */ public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } /** * checkPositionIndex(index); 檢查傳入的參數是否合法 */ public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); // 將集合轉換爲數組 Object[] a = c.toArray(); // numNew 存儲數組的長度 int numNew = a.length; // 若是 c:待添加的集合爲null,直接返回false,不進行後面的操做 if (numNew == 0) return false; // pred:指代 待添加節點的前一個節點 // succ:指的是待添加節點的位置 Node<E> pred, succ; // 若是index==size,說明此時須要添加LinkedList的集合中每個元素都是在LinkedList最後面。因此把succ設置爲null, pred指向尾結點 // 不然的話succ指向插入待插入位置的節點。這裏用到了node(index)方法,pred指向succ節點的前一個節點 if (index == size) { // 新添加的元素的位置是位於LinkedList最後一個元素的後面,也就是在LinkedList尾部追加元素 succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } /** * 遍歷數組中的每個元素。在每次遍歷的時候,都新建一個節點,該節點的值存儲數組a中遍歷的值,該節點的prev存儲pred節點,next設置爲null。 * 接着判斷該節點的前一個節點是否爲空,若是爲空的話,則把當前節點設置爲頭結點 * 不然的話就把當前節點的前一個節點的next值設置爲當前節點。最後把pred指向當前節點,方便後續節點的添加 */ 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; } /** * 當succ爲null時,也就是新添加的節點位於LinkedList集合的最後一個元素的後面。 * 上面的for遍歷的a的全部元素,此時的pred指向的是Linked中的最後一個元素,因此把last指向pred指向的節點 * 當不爲空的時候,代表在LinkedList集合中添加的元素,須要把pred的next指向succ上,succ的prev指向pred */ if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } // 從新設置集合的大小 size += numNew; // 修改的次數-自增。 modCount++; return true; }
/** * 連接e做爲第一個元素。 */ private void linkFirst(E e) { final Node<E> f = first;// LinkedList中的第一個元素first賦給 f // 組建新的node,新添加的元素的succ(後指針指向給 f) final Node<E> newNode = new Node<>(null, e, f); // 將新插入的節點e,賦給first first = newNode; // 若是 f== null ;說明是空鏈表,把新節點設置爲尾結點 if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }
/** * 連接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節點爲須要插入的節點。(也就是說,該節點便是頭結點也是尾節點last) first = newNode; else // 不是空鏈表的狀況:將原來的尾部節點(如今是卻是第二個節點)的next指向須要插入的節點 l.next = newNode; size++; // 更新鏈表大小和修改次數,插入完畢 modCount++; }
/** * 將元素e插入到非空節點succ以前。 */ void linkBefore(E e, Node<E> succ) { // assert succ != null; // 建立一個pred變量指向succ節點的前一個節點 final Node<E> pred = succ.prev; // 建立一個新節點,他的prev設置爲咱們新建的pred變量,後節點設置爲succ final Node<E> newNode = new Node<>(pred, e, succ); // succ的上節點(prev)指向新建的節點 succ.prev = newNode; // 判斷succ的前節點是否爲null,爲null,則把新節點設置爲鏈表的頭結點 if (pred == null) first = newNode; else // 不爲空把,succ的前一個節點的next指向新節點 pred.next = newNode; size++; modCount++; }
assert f == first && f != null;
參數f是頭結點,並且f不能爲null/** * 解除非空第一個節點f的連接。 */ private E unlinkFirst(Node<E> f) { // assert f == first && f != null; // 定義一個變量element,值爲待刪除節點的值。 final E element = f.item; // 定義變量next,值爲:待刪除的節點的下一個節點 final Node<E> next = f.next; // f節點的值設置爲空 f.item = null; f.next = null; // help GC // 將變量next設置爲頭節點 first = next; // 判斷f的下一個節點是否爲空,爲空:把last設置爲空 if (next == null) last = null; else // 不爲空,將next的前節點設置爲空。next爲頭結點 next.prev = null; size--; modCount++; return element; }
/** * Unlinks non-null last node l. */ private E unlinkLast(Node<E> l) { // assert l == last && l != null; // 建立變量element,值爲:待刪除的節點的值 final E element = l.item; // 建立變量 prev:值爲:待刪除的節點的前節點 final Node<E> prev = l.prev; // 待刪除的節點的值賦空 l.item = null; l.prev = null; // help GC // 將last指向 新建的節點 prev last = prev; // 判斷待刪除的節點的前節點是否爲空,爲空:該鏈表則爲空鏈表,將頭結點first賦null值 if (prev == null) first = null; else // 不爲空,將待刪除的前節點的next指向 null prev.next = null; size--; modCount++; return element;//返回刪除的節點的值 }
/** * Unlinks non-null node x. */ E unlink(Node<E> x) { // assert x != null; // 變量 element:值:要刪除的節點的值 final E element = x.item; // 新建變量:next,值:要刪除的節點的下一個節點 final Node<E> next = x.next; // 新建變量:prev,值:要刪除的節點的上一個節點 final Node<E> prev = x.prev; // 判斷要刪除的節點的上一個節點爲空,爲空:則刪除的是頭結點,將first指向新建的next if (prev == null) { first = next; } else { // 不爲空:將要刪除的節點的上一個節點的next指向要刪除的節點的下一個節點 prev.next = next; x.prev = null; } // 判斷要刪除的節點的下一個節點是否爲空,爲空:則刪除的尾結點,將last指向prev,也就是指向要刪除的節點的上一個節點 if (next == null) { last = prev; } else { // 不爲空,將要刪除的節點的下一個節點的上節點指向-》要刪除的節點的上個節點 next.prev = prev; x.next = null; } // 這一步就把要刪除的節點賦了空值,有助於gc回收 x.item = null; size--; modCount++; return element; }
/** * 返回指定元素索引處的(非空)節點。 */ 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; } }
以上是一些輔助方法,在LinkedList的add,get,remove等方法中都會使用到相應的方法。函數
public boolean contains(Object o) { return indexOf(o) != -1; } public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
indexOf(Object o)方法中分兩種狀況,源碼分析
首先判斷傳入的參數 o 是否是空,測試
我的認爲看懂了上面的方法,其餘的方法都簡單明瞭。優化
因此,若是待插入、刪除的元素是在數據結構的前半段,尤爲是很是靠前的位置的時候,LinkedList的效率將大大快過ArrayList,由於ArrayList將批量copy大量的元素;越日後,對於LinkedList來講,由於它是雙向鏈表,因此在第2個元素後面和在倒數第2個元素後面插入一個元素在效率上基本沒有差異,可是ArrayList因爲copy的元素愈來愈少,操做速度確定會愈來愈快this
測試:spa
步驟:一、分別建立一個LinkedList和一個ArrayList; 二、分別插入100000條數據 三、分別使用for,foreach。iterator遍歷,記錄所用時間
LinkedList<String> linkedList = new LinkedList<String>(); ArrayList<String> arrayList = new ArrayList<String>(); for(int i = 0; i < 100000; i ++) { linkedList.add("linkedList -- " + i); arrayList.add("arrayList -- " + i); } // ------------------------foreach----------------------- long befor = System.currentTimeMillis(); for(String ii : arrayList){ } long after = System.currentTimeMillis(); System.out.println("Arraylist使用foreach遍歷的時間是:"+(after-befor)+"ms"); befor = System.currentTimeMillis(); for(String ii : linkedList){ } after = System.currentTimeMillis(); System.out.println("Linkedlist使用foreach遍歷的時間:"+(after-befor)+"ms"); // ------------------------Iterator----------------------- Iterator<String> arrayListIterator = arrayList.iterator(); befor = System.currentTimeMillis(); while(arrayListIterator.hasNext()) { arrayListIterator.next(); } after = System.currentTimeMillis(); System.out.println("Arraylist使用Iterator遍歷的時間是:"+(after-befor)+"ms"); Iterator<String> linkedListIterator = arrayList.iterator(); befor = System.currentTimeMillis(); while(linkedListIterator.hasNext()) { linkedListIterator.next(); } after = System.currentTimeMillis(); System.out.println("Linkedlist使用Iterator遍歷的時間是:"+(after-befor)+"ms"); // -------------------------for---------------------------- befor = System.currentTimeMillis(); for (int i = 0; i < arrayList.size(); i++) { arrayList.get(i); } after = System.currentTimeMillis(); System.out.println("Arraylist使用for遍歷的時間是:"+(after-befor)+"ms"); befor = System.currentTimeMillis(); for (int i = 0; i < linkedList.size(); i++) { linkedList.get(i); } after = System.currentTimeMillis(); System.out.println("Linkedlist使用for遍歷的時間是:"+(after-befor)+"ms"); // 第1次運行結果 /** * Arraylist使用foreach遍歷的時間是:7ms * Linkedlist使用foreach遍歷的時間:10ms * Arraylist使用Iterator遍歷的時間是:2ms * Linkedlist使用Iterator遍歷的時間是:1ms * Arraylist使用for遍歷的時間是:0ms * Linkedlist使用for遍歷的時間是:35070ms */ // 第2次運行結果 /** * Arraylist使用foreach遍歷的時間是:2ms * Linkedlist使用foreach遍歷的時間:4ms * Arraylist使用Iterator遍歷的時間是:1ms * Linkedlist使用Iterator遍歷的時間是:1ms * Arraylist使用for遍歷的時間是:0ms * Linkedlist使用for遍歷的時間是:37262ms */
10萬條記錄。能夠看出,不論是foreach仍是Iterator,速度都差異不大,可是對於for循環時,linkedList的for循環遍歷的時間很長指針
LinkedList的get();
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; } }
因爲LinkedList是雙鏈表,因此經過index去獲取的時候回判斷index是在前半段仍是後半段,前半段正序遍歷,後半段倒序遍歷,這也是Linked優化的部分。那麼爲何使用不一樣的for循環遍歷LinkedList會很慢呢?
例:
以此類推,也就是說,LinkedList在get任何數據的時候,會把前面的數據走一遍,隨着LinkedList的容量愈來愈大,時間的消耗就會愈來愈長 因此說建議使用迭代器或者foreach循環去遍歷LinkedList,這種方式是直接按照地址去找數據的,將大大提高遍歷LinkedList的效率。