###LinkedList源碼解析java
[TOC]git
####------簡介github
####------結構 LinkedList繼承自AbstractSequentialList<E>,實現了List<E>, Deque<E>, Cloneable, java.io.Serializable接口。 下面是LinkedList中的內部類以及重要的屬性:數組
####------內部類講解安全
private static class Entry<E> { E element; Entry<E> next; Entry<E> previous; }//雙向鏈表,因此定義了先後指針
內部類listItr 看簡介的說明,咱們會發現此類實現了ListIterator<E>接口,所以LinkedList集合能夠迭代其中的元素,他經過複寫及口中的hasNext()等方法找到鏈表中的下一個元素,直到鏈表爲空。數據結構
屬性 private Entry<E> lastReturned = header;//記錄上一個遍歷節點 private Entry<E> next;//用來遍歷元素 private int nextIndex;//記錄當前元素的下標 private int expectedModCount = modCount;(A-最後一塊兒講)多線程
構造方法 ListItr(int index) 此類的構造方法很重要。它的主要做用是找到index下標的元素,這樣就能夠向此元素以前或者此元素以後遍歷全部了。 實現原理:若是傳進的索引值index小於0或者大於LinkedList的size,那麼咱們就會看到常常會出現的異常IndexOutOfBoundsException。若是index小於數組大小(size)的一半的話,將頭指針附給next,而後從數組的第0個遍歷到index,找到下標爲index的元素。若是index的值大於等於數組值的一半,那麼,將尾指針付給next,而後從後向前遍歷集合知道找到index對應的元素,這樣一來,咱們每一次遍歷都不會超過數組大小的一半,大大的提升了效率。 下面來看他的實現:併發
ListItr(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index + ", Size: "+size); if (index < (size >> 1)) { next = header.next; for (nextIndex=0; nextIndex<index; nextIndex++) next = next.next; } else { next = header; for (nextIndex=size; nextIndex>index; nextIndex--) next = next.previous; } }
能夠看到它計算數組的一半的方法:(size >> 1)是將size右移一位,在計算機中,數字以二進制的形式表示的,右移一位表示將size除以2,相反的左移一位表明乘以2.異步
public void add(E e) { checkForComodification();//B-隨後一塊兒講 lastReturned = header; addBefore(e, next); nextIndex++; expectedModCount++;//D-隨後一塊兒講 } 看看addBefore的代碼: private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++;//C-最後一期講 return newEntry; }
**2.**private E remove(Entry<E> e) 刪除元素的方法很簡單,就是雙向鏈表的刪除操做,但必定要注意順序,刪除的那四句代碼的順序不能夠改變。刪除之後,將集合的大小減去一。this
private E remove(Entry<E> e) { if (e == header) throw new NoSuchElementException(); E result = e.element; e.previous.next = e.next; e.next.previous = e.previous; e.next = e.previous = null; e.element = null; size--; modCount++; return result; }
**3.**public E previous() 此方法主要是能夠逆序找到上一個元素。若是表示當前元素下標的屬性nextIndex等於0,表示鏈表爲空,因此會拋出異常NoSuchElementException;若果不爲空的話,將記錄上一個元素的屬性lastReturned標記爲要尋找的元素(lastReturned = next = next.previous;)固然,因爲是逆序遍歷,當前元素的下表也是要減的。
public E previous() { if (nextIndex == 0) throw new NoSuchElementException(); lastReturned = next = next.previous; nextIndex--; checkForComodification(); return lastReturned.element; }
一樣的,checkForComodification();也到最後在一塊兒講
private class DescendingIterator implements Iterator { final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } }
####------屬性expectedModCount屬性的講解
private void checkForComodification() { if (this.modCount != l.modCount) throw new ConcurrentModificationException(); }
我麼能夠看到,這個類是檢查子類中的modCount是否與他的父類中的modCount是否一致,若是不一致就會拋出異常ConcurrentModificationException;他在這裏的做用咱們講完C和D時再來分析。
如今咱們來說一講這樣做的好處 當同步操做或者單線程時,集合的操做是沒有問題的,那若是在多線程併發的時候呢?若是一個操做集合的一個線程在不停的刪除元素,一個集合在不停的添加元素,那麼這個集合還有什麼意義呢?爲了使這種狀況不發生,JDK的設計者要求使用時不能夠異步使用,若是你非得異步使用,那麼就會拋出異常。那JDK是怎樣阻止使用者不能異步使用的呢?就是上面的ABCD的做用了。當用戶在添加一個元素時,先檢查父類的modCount與子類的expectedModcount是否相同(調用CheckForComodification()方法),若是不相同,說明有多個線程在操做集合,那麼就拋出異常,若是相同,將父類的modCount加上一,再將子類的expectedModCount再加上一,這中間也有執行的代碼。正由於這樣,若是進行異步造做的話,就有可能致使程序沒有按照預想的順序執行,那麼就頗有可能致使弗雷德modCount與子類的expectedModCount不一致,這樣的話在添加時就會拋出異常。
####------主要方法講解
其實在LinkedList中,只要掌握了他的數據結構以及內部類,那麼接下來LinkedList中的對元素的操做都是圍繞着這些內部類來實現的。 下面咱們來看看LinkedList中的方法
public boolean add(E e) { addBefore(e, header); return true; } 若是前面的看懂了,那應該沒有任何難度了。 - public E remove(int index) 此方法從集合中刪除指定的元素。
public E remove(int index) { return remove(entry(index)); }
也是調用前面內部類中的方法。這裏就不詳細講解了,又不理解的看看內部類中的講解。 ####------實現的接口的講解 - **List接口** 有序的 collection(也稱爲序列)。此接口的用戶能夠對列表中每一個元素的插入位置進行精確地控制。用戶能夠根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。 與 set 不一樣,列表一般容許重複的元素。更正式地說,列表一般容許知足 e1.equals(e2) 的元素對 e1 和 e2,而且若是列表自己容許 null 元素的話,一般它們容許多個 null 元素。(來自JDK) - **Deque接口** 一個線性 collection,支持在兩端插入和移除元素。至關於一個隊列,的確LinkedList中有對隊列操做的方法,即,你能夠將一個LinkedList當成一個隊列來使用。 隊列的方法主要有如下: **1.**public void push(E e) 和隊列相同,若是須要插入一個元素的話就在鏈表的尾部插入一個元素,一樣的調用的時候仍然是使用上面內部類中的操做。
public void addFirst(E e) { addBefore(e, header.next);//上面講過 }
咱們瀏覽內部類的講解能夠看到addBefore是在鏈表的尾部插入元素,正好符合隊列的要求。 **2.** public E pop() 若是須要刪除一個元素,那麼就要在鏈表的頭部刪除。
public E pop() { return removeFirst(); } //咱們來看一下removeFist的實現 public E removeFirst() { return remove(header.next); }
咱們能夠看到又再次調用了內部類中的remove方法,可是咱們注意remove中的參數是header。next,上面說過,LinkedList中的雙向鏈表是將頭指針指向尾部的,因此header.next就是頭部,將頭節點傳入就刪除了頭部。實現了隊列的思想。 - **Cloneable接口** 此類實現了 Cloneable 接口,以指示 Object.clone() 方法能夠合法地對該類實例進行按字段複製。 若是在沒有實現 Cloneable 接口的實例上調用 Object 的 clone 方法,則會致使拋出 CloneNotSupportedException 異常。 按照慣例,實現此接口的類應該使用公共方法重寫 Object.clone(它是受保護的)。 LinkedList集合的clone是淺克隆,通常來講clone時是遞歸的先克隆對象自己,而後逐層克隆對象的屬性,方法等等。可是,在LinkedList中,克隆時只克隆LinkedList自己,而它裏面的元素不進行遞歸克隆,因此說LinkedList是淺克隆的。 - **java.io.Serializable** 使用Serializable接口可使此集合序列化,當內存不夠時,能夠講對象寫成二進制的文件放進硬盤中,須要時再從硬盤中讀出,而後恢復。這樣能夠節省內存。集合屬於比較大的對象,若是此對象已被加載到虛擬機中,可是又不常用,因爲他也是一個強引用,垃圾回收器不能將它回收,這時,若是他存活時間過長,被垃圾回收期已經轉移到老年代時,長時間的存活會引發GC,GC是一個耗時的過程,咱們不肯意遇到這樣的狀況,因此將它序列化,而後須要的時候再將它使用IO從硬盤中讀取出來。 **更多java源碼解析:** [https://github.com/ccqy66/JDK-s-source-parser](http://) <!--若有不妥,歡迎指正。-->