LinkedList源碼解析

###LinkedList源碼解析java

[TOC]git

####------簡介github

  • LinkedList底層是使用雙線鏈表來實現的,將數組添加到這個集合或者是從集合刪除其實都是對雙向鏈表增長節點和刪除及節點的操做。
  • LinkedList實現的類AbstractSequentialList<E>中定義的modCount屬性使得繼承自它的集合不可以異步的進行合集的增長刪除等等操做,即操做是線程不安全的。

####------結構 LinkedList繼承自AbstractSequentialList<E>,實現了List<E>, Deque<E>, Cloneable, java.io.Serializable接口。 下面是LinkedList中的內部類以及重要的屬性:數組

  • 內部類
  • private class ListItr implements ListIterator<E>
  • private static class Entry<E>
  • private class DescendingIterator implements Iterator
  • 主要屬性
  • private transient Entry<E> header = new Entry<E>(null, null, null);
  • private transient int size = 0;
  • 構造方法
  • public LinkedList()
  • public LinkedList(Collection<? extends E> c)
  • 主要講的方法
  • public boolean add(E e)
  • public boolean remove(Object o)
  • public void push(E e)
  • public E pop()

####------內部類講解安全

  • 靜態內部類Entry 在以上說過,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.異步

  • 實現ListIterator<E>的方法 因爲此內部類實現了ListIterator<E>接口因此複寫了其中的方法,咱們如今主要講三個方法: **1.**public void add(E e) 此方法的做用是在集合中添加一個元素。添加的方法使用的是尾插法,因此lastReturned先被附爲header(這說明LinkedList使用雙向鏈表時將頭節點位置放在鏈表的尾部),而後調用方法addBefore將元素e插入鏈表尾部,將當前元素的位置加1.
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();也到最後在一塊兒講

  • 內部類DescendingIterator 這個內部類是實現集合的減序遍歷的,實現了Interator接口。一樣的,他遍歷的時候就使用上一個內部類ListItr複寫的方法previous就能夠獲得上一個元素了。
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屬性的講解

  • A處的expectedModCount=modCount modCount是LinkedList繼承自他的父類的屬性。這是一個很簡單的賦值語句,看AbstractList的源碼咱們能夠知道屬性modCount的類型是pretected類型的,他就是用來給子類繼承的。
  • B處的CheckForComodification();方法的講解 在內部類ListItr中的add方法中,首先調用了這個方法,咱們來看看這個方法是幹什麼的
private void checkForComodification() {
        if (this.modCount != l.modCount)
            throw new ConcurrentModificationException();
    }

我麼能夠看到,這個類是檢查子類中的modCount是否與他的父類中的modCount是否一致,若是不一致就會拋出異常ConcurrentModificationException;他在這裏的做用咱們講完C和D時再來分析。

  • C處的modCount++的講解 這句代碼在add方法調用的addBefore中出現,咱們能夠看到,每當集合中增長一個元素的時候,調用addBefore方法都會現將父類AbstractList中的modCount加上一。這個做用咱們接下來說。
  • D處的ecpectedModCount++的講解 這句代碼一樣出如今add方法中,我麼能夠看到在addBefore中將父類的modCount加上一之後再講子類LinkedList中的expectedModCount的值再加上一。

如今咱們來說一講這樣做的好處 當同步操做或者單線程時,集合的操做是沒有問題的,那若是在多線程併發的時候呢?若是一個操做集合的一個線程在不停的刪除元素,一個集合在不停的添加元素,那麼這個集合還有什麼意義呢?爲了使這種狀況不發生,JDK的設計者要求使用時不能夠異步使用,若是你非得異步使用,那麼就會拋出異常。那JDK是怎樣阻止使用者不能異步使用的呢?就是上面的ABCD的做用了。當用戶在添加一個元素時,先檢查父類的modCount與子類的expectedModcount是否相同(調用CheckForComodification()方法),若是不相同,說明有多個線程在操做集合,那麼就拋出異常,若是相同,將父類的modCount加上一,再將子類的expectedModCount再加上一,這中間也有執行的代碼。正由於這樣,若是進行異步造做的話,就有可能致使程序沒有按照預想的順序執行,那麼就頗有可能致使弗雷德modCount與子類的expectedModCount不一致,這樣的話在添加時就會拋出異常。

####------主要方法講解

其實在LinkedList中,只要掌握了他的數據結構以及內部類,那麼接下來LinkedList中的對元素的操做都是圍繞着這些內部類來實現的。 下面咱們來看看LinkedList中的方法

  • public boolean add(E e) 此方法時添加一個元素到集合中。咱們能夠看到他的實現代碼就是調用內部類中的方法來實現他的功能的。
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://)


<!--若有不妥,歡迎指正。-->
相關文章
相關標籤/搜索