JDK1.8源碼學習-LinkedListjava
目錄node
1、LinkedList簡介數組
LinkedList是一個繼承於AbstractSequentialList的雙向鏈表,是能夠在任意位置進行插入和移除操做的有序序列。數據結構
LinkedList基於鏈表實現,在存儲元素的過程當中,無需像ArrayList那樣進行擴容,可是有得必有失,LinkedList存儲元素的節點須要額外的空間存儲前驅和後繼的引用。此外,LinkedList在鏈表頭部和尾部插入效率比較高,可是在指定位置進行插入操做時,效率通常。緣由是在指定位置插入須要定位到該位置處的節點,此操做的時間複雜度爲O(N)。函數
2、LinkedList工做原理源碼分析
在LinkedList中,每個元素都是Node存儲,Node擁有一個存儲值的item和一個前驅prev和一個後繼next,源碼以下:學習
// 鏈表結構 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; } }
3、LinkedList源碼分析this
3.一、繼承關係分析spa
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList繼承自AbstractSequentialList,實現了List、Deque、Cloneable、Serializable接口,其中List接口中定義了一些隊列的基本的操做,Deque接口可以使LinkedList看成雙端隊列使用,Cloneable接口可使LinkedList調用clone()方法,進行淺層次的拷貝,Serializable接口可使LinkedList實現序列化。指針
3.二、成員變量分析
//實現Serilizable接口時,將不須要序列化的屬性前添加關鍵字transient, //序列化對象的時候,這個屬性就不會序列化到指定的目的地中。 transient int size = 0; //指向首節點 transient Node<E> first; //指向最後一個節點 transient Node<E> last;
3.三、構造函數分析
3.3.1 無參構造函數
//無參的構造函數 public LinkedList() { }
3.3.2 傳入集合c的構造函數
public LinkedList(Collection<? extends E> c) { this(); // 將集合添加到鏈表中去 addAll(c); } public boolean addAll(Collection<? extends E> c) { // 從鏈表尾巴開始添加集合中的元素 return addAll(size, c); } public boolean addAll(int index, Collection<? extends E> c) { // 1.添加位置的下標的合理性檢查 checkPositionIndex(index); // 2.將集合轉換爲Object[]數組對象 Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; // 3.獲得插入位置的前繼節點和後繼節點 Node<E> pred, succ; if (index == size) { // 從尾部添加的狀況:前繼節點是原來的last節點;後繼節點是null succ = null; pred = last; } else { // 從指定位置(非尾部)添加的狀況:前繼節點就是index位置的節點,後繼節點是index位置的節點的前一個節點 succ = node(index); pred = succ.prev; } // 4.遍歷數據,將數據插入 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; } if (succ == null) { // 若是是從尾部開始插入的,則把last置爲最後一個插入的元素 last = pred; } else { // 若是不是從尾部插入的,則把尾部的數據和以前的節點連起來 pred.next = succ; succ.prev = pred; } size += numNew; // 鏈表大小+num modCount++; // 修改次數加1 return true; }
3.四、add()方法分析
LinkedList中的add方法有兩個,一個是add(E e)方法,一個是add(int index, E element)方法。
3.4.一、add(E e)方法
// 做用:將元素添加到鏈表尾部 public boolean add(E e) { linkLast(e); return true; }
void linkLast(E e) { final Node<E> l = last; // 獲取尾部元素 final Node<E> newNode = new Node<>(l, e, null); // 以尾部元素爲前繼節點建立一個新節點 last = newNode; // 更新尾部節點爲須要插入的節點 if (l == null) // 若是空鏈表的狀況:同時更新first節點也爲須要插入的節點。(也就是說:該節點既是頭節點first也是尾節點last) first = newNode; else // 不是空鏈表的狀況:將原來的尾部節點(如今是倒數第二個節點)的next指向須要插入的節點 l.next = newNode; size++; // 更新鏈表大小和修改次數,插入完畢 modCount++; }
3.4.二、add(int index,E element)方法
// 做用:在指定位置添加元素 public void add(int index, E element) { // 檢查插入位置的索引的合理性 checkPositionIndex(index); if (index == size) // 插入的狀況是尾部插入的狀況:調用linkLast()。 linkLast(element); else // 插入的狀況是非尾部插入的狀況(中間插入):linkBefore()見下面。 linkBefore(element, node(index)); } private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private boolean isPositionIndex(int index) { return index >= 0 && index <= size; } 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的前節點,後接點是succ節點 succ.prev = newNode; // 更新插入位置(succ)的前置節點爲新節點 if (pred == null) // 若是pred爲null,說明該節點插入在頭節點以前,要重置first頭節點 first = newNode; else // 若是pred不爲null,那麼直接將pred的後繼指針指向newNode便可 pred.next = newNode; size++; modCount++; }
3.五、get(int index)方法分析
public E get(int index) { // 元素下表的合理性檢查 checkElementIndex(index); // node(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; }
3.六、remove(int index)方法分析
// 做用:移除指定位置的元素 public E remove(int index) { // 移除元素索引的合理性檢查 checkElementIndex(index); // 將節點刪除 return unlink(node(index)); } E unlink(Node<E> x) { // assert x != null; final E element = x.item; // 獲得指定節點的值 final Node<E> next = x.next; // 獲得指定節點的後繼節點 final Node<E> prev = x.prev; // 獲得指定節點的前繼節點 // 若是prev爲null表示刪除是頭節點,不然就不是頭節點 if (prev == null) { first = next; } else { prev.next = next; x.prev = null; // 置空需刪除的指定節點的前置節點(null) } // 若是next爲null,則表示刪除的是尾部節點,不然就不是尾部節點 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; // 置空需刪除的指定節點的後置節點 } // 置空需刪除的指定節點的值 x.item = null; size--; // 數量減1 modCount++; return element; }
3.七、clear()方法分析
// 清空鏈表 public void clear() {// 進行for循環,進行逐條置空;直到最後一個元素 for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } // 置空頭和尾爲null first = last = null; size = 0; modCount++; }
3.八、indexOf(Object o)方法分析
// 返回列表中第一次出現o的位置,若不存在則返回-1 public int indexOf(Object o) { int index = 0; // 若是元素爲null,進行以下循環判斷 if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { // 元素不爲null.進行以下循環判斷 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
3.九、addFirst(E e)方法分析
// 做用:在鏈表頭插入指定元素 public void addFirst(E e) { linkFirst(e); } private void linkFirst(E e) { final Node<E> f = first; // 獲取頭部元素 final Node<E> newNode = new Node<>(null, e, f); // 建立新的頭部元素(原來的頭部元素變成了第二個) first = newNode; // 鏈表頭部爲空,(也就是鏈表爲空) if (f == null) last = newNode; // 頭尾元素都是e else f.prev = newNode; // 不然就更新原來的頭元素的prev爲新元素的地址引用 size++; modCount++; }
3.十、addLast(E e)方法分析
// 做用:在鏈表尾部添加元素e public void addLast(E e) { // 上面已講解過,參考上面。add()方法 linkLast(e); }
3.十一、push(E e)方法分析
// 做用:將元素添加到頭部(入棧) public void push(E e) { addFirst(e); } public void addFirst(E e) { linkFirst(e); } private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }
3.十二、getFirst()方法分析
// 做用:獲得頭元素 public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; }
3.1三、getLast()方法分析
// 做用:獲得尾部元素 public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }
3.1四、peek()方法分析
// 做用:返回頭元素,而且不刪除。若是不存在也不錯,返回null public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; }
3.1五、peekFirst()方法分析
// 做用:返回頭元素,而且不刪除。若是不存在也不錯,返回null public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; }
3.1六、peekLast()方法分析
// 做用:返回尾元素,若是爲null,則就返回null public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; }
3.1七、poll()方法分析
// 做用:返回頭節點元素,並刪除頭節點。並將下一個節點設爲頭節點。 public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
3.1八、pollFirst()方法分析
// 做用:返回頭節點,並刪除頭節點,並將下一個節點設爲頭節點。 public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
3.1九、pollLast()方法分析
// 做用:返回尾節點,而且將尾節點刪除,並將尾節點的前一個節點置爲尾節點 public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); }
3.20、pop()方法分析
// 做用:刪除頭節點,若是頭結點爲null.則拋出異常(出棧) public E pop() { return removeFirst(); } public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }
4、利用LinkedList實現棧操做
public class Stack<T> { private LinkedList<T> stack; //無參構造函數 public Stack() { stack=new LinkedList<T>(); } //構造一個包含指定collection中全部元素的棧 public Stack(Collection<? extends T> c) { stack=new LinkedList<T>(c); } //入棧 public void push(T t) { stack.addFirst(t); } //出棧 public T pull() { return stack.remove(); } //棧是否爲空 boolean isEmpty() { return stack.isEmpty(); } //打印棧元素 public void display() { for(Object o:stack) System.out.println(o); } }
5、LinkedList總結
ArrayList和LinkedList的大體區別:
1.ArrayList是基於動態數組的數據結構,LinkedList是基於鏈表的數據結構。
2.對於隨機訪問的get和set方法,ArrayList要優於LinkedList,由於LinkedList要移動指針。
3.數組遍歷的方式ArrayList推薦使用for循環,而LinkedList則推薦使用forearch,若是使用for循環,效率會很慢。
通常來講,對於新增和刪除操做add和remove,LinkedList比較佔優點,由於ArrayList要移動數據,可是這樣說是有一些問題的。
LinkedList作插入、刪除的時候,慢在尋址,快在只須要改變先後的Node的引用地址;
ArrayList作插入、刪除的時候,慢在數組元素的批量copy,快在尋址。
因此,若是待插入、刪除的元素是在數據結構的前半段尤爲是很是靠前的位置的時候,LinkedList的效率將大大快過ArrayList,由於ArrayList將批量copy大量的元素;越日後,對於LinkedList來講,由於它是雙向鏈表,因此在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差異,可是ArrayList因爲要批量copy的元素愈來愈少,操做速度上必然追上乃至超過LinkedList。
在實際中應該怎樣選擇呢?
若是你十分肯定你插入、刪除的元素是在前半段,使用LinkedList,反之使用ArrayList。
若是不肯定,建議使用LinkedList,由於LinkedList總體插入、刪除的執行效率比較穩定,沒有ArrayList這種越日後越快的狀況,且ArrayList可能會進行擴容,擴容是一件即消耗時間又消耗空間的操做。