1. 程式人生 > >ArrayList實現原理以及原始碼解析(JDK1.6)

ArrayList實現原理以及原始碼解析(JDK1.6)

ArrayList實現原理以及原始碼解析(JDK1.6)

1、ArrayList
ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。
ArrayList不是執行緒安全的,只能用在單執行緒環境下。
多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList類。
也可以使用concurrent併發包下的CopyOnWriteArrayList類。
ArrayList實現了Serializable介面,因此它支援序列化,能夠通過序列化傳輸。
實現了RandomAccess介面,支援快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable介面,能被克隆。
每個ArrayList例項都有一個容量,該容量是指用來儲存列表元素的陣列的大小。它總是至少等於列表的大小。
隨著向ArrayList中不斷新增元素,其容量也自動增長。自動增長會帶來資料向新陣列的重新拷貝,因此,如果可預知資料量的多少,可在構造ArrayList時指定其容量。
在新增大量元素前,應用程式也可以使用ensureCapacity操作來增加ArrayList例項的容量,這可以減少遞增式再分配的數量。 
注意,此實現不是同步的。如果多個執行緒同時訪問一個ArrayList例項,而其中至少一個執行緒從結構上修改了列表,那麼它必須保持外部同步。
這通常是通過同步那些用來封裝列表的物件來實現的。
建議在單執行緒中才使用ArrayList,而在多執行緒中可以選擇Vector或者CopyOnWriteArrayList。

2、 ArrayList的屬性
ArrayList它實現List介面、底層使用陣列儲存所有元素。其操作基本上是對陣列的操作。
ArrayList定義只定義類兩個私有屬性:
    /** 
      * The array buffer into which the elements of the ArrayList are stored. 
      * The capacity of the ArrayList is the length of this array buffer. 
      */  
     private transient Object[] elementData;  
   
     /** 
      * The size of the ArrayList (the number of elements it contains). 
      * 
      * @serial 
      */  
     private int size;
elementData儲存ArrayList內的元素,size表示它包含的元素的數量(是指記憶體已存在的“實際元素的長度” 而“空元素不被計算”)。

transient關鍵字:
Java的serialization提供了一種持久化物件例項的機制。
當持久化物件時,可能有一個特殊的物件資料成員,我們不想用serialization機制來儲存它。
為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
被標記為transient的屬性在物件被序列化的時候不會被儲存。
transient例項:
	class UserInfo implements Serializable {
		private static final long serialVersionUID = 996890129747019948L;
		private String name;
		private transient String psw;


		public UserInfo(String name, String psw) {
			this.name = name;
			this.psw = psw;
		}


		public String toString() {
			return "name=" + name + ", psw=" + psw;
		}
	}
	public class TestTransient {
		public static void main(String[] args) {
			UserInfo userInfo = new UserInfo("張三", "123456");
			System.out.println(userInfo);
			try {
				// 序列化,被設定為transient的屬性沒有被序列化
				ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
						"UserInfo.out"));
				o.writeObject(userInfo);
				o.close();
			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}
			try {
				// 重新讀取內容
				ObjectInputStream in = new ObjectInputStream(new FileInputStream(
						"UserInfo.out"));
				UserInfo readUserInfo = (UserInfo) in.readObject();
				// 讀取後psw的內容為null
				System.out.println(readUserInfo.toString());
			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}
3、構造方法: 
ArrayList提供了三種方式的構造器,可以構造一個預設初始容量為10的空列表、構造一個指定初始容量的空列表
以及構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。
// ArrayList帶容量大小的建構函式。
	public ArrayList(int initialCapacity) {
		super();
		if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal Capacity: "
					+ initialCapacity);
		// 新建一個數組
		this.elementData = new Object[initialCapacity];
	}


	// ArrayList無參建構函式。預設容量是10。
	public ArrayList() {
		this(10);
	}


	// 建立一個包含collection的ArrayList
	public ArrayList(Collection<? extends E> c) {
		elementData = c.toArray();
		size = elementData.length;
		if (elementData.getClass() != Object[].class)
			elementData = Arrays.copyOf(elementData, size, Object[].class);
	}

4、 元素儲存:
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、 addAll(int index, Collection<? extends E> c)這些新增元素的方法。

	public E set(int index, E element) {
		RangeCheck(index);
		E oldValue = (E) elementData[index];
		elementData[index] = element;
		return oldValue;
	}


	// 將指定的元素新增到此列表的尾部。
	public boolean add(E e) {
		ensureCapacity(size + 1);
		elementData[size++] = e;
		return true;
	}


	// 將指定的元素插入此列表中的指定位置。
	// 如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加1)。
	public void add(int index, E element) {
		if (index > size || index < 0)
			throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
					+ size);
		// 如果陣列長度不足,將進行擴容。
		ensureCapacity(size + 1); // Increments modCount!!
		// 將 elementData中從Index位置開始、長度為size-index的元素,
		// 拷貝到從下標為index+1位置開始的新的elementData陣列中。
		// 即將當前位於該位置的元素以及所有後續元素右移一個位置。
		System.arraycopy(elementData, index, elementData, index + 1, size
				- index);
		elementData[index] = element;
		size++;
	}


	// 按照指定collection的迭代器所返回的元素順序,將該collection中的所有元素新增到此列表的尾部。
	public boolean addAll(Collection<? extends E> c) {
		Object[] a = c.toArray();
		int numNew = a.length;
		ensureCapacity(size + numNew); // Increments modCount
		System.arraycopy(a, 0, elementData, size, numNew);
		size += numNew;
		return numNew != 0;
	}


	// 從指定的位置開始,將指定collection中的所有元素插入到此列表中。
	public boolean addAll(int index, Collection<? extends E> c) {
		if (index > size || index < 0)
			throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
					+ size);


		Object[] a = c.toArray();
		int numNew = a.length;
		ensureCapacity(size + numNew); // Increments modCount


		int numMoved = size - index;
		if (numMoved > 0)
			System.arraycopy(elementData, index, elementData, index + numNew,
					numMoved);


		System.arraycopy(a, 0, elementData, index, numNew);
		size += numNew;
		return numNew != 0;
	}
5、元素讀取:
// 返回此列表中指定位置上的元素。  
public E get(int index) {  
RangeCheck(index);  
return (E) elementData[index];  
 }

6、 元素刪除:
ArrayList提供了根據下標或者指定物件兩種方式的刪除功能。
romove(int index):
	// 移除此列表中指定位置上的元素。  
	public E remove(int index) {  
		RangeCheck(index);  	  
		modCount++;  
		E oldValue = (E) elementData[index];  
	  
		int numMoved = size - index - 1;  
		if (numMoved > 0)  
			System.arraycopy(elementData, index+1, elementData, index, numMoved);  
		elementData[--size] = null; // Let gc do its work  
	  
		return oldValue;  
	 }
首先是檢查範圍,修改modCount,保留將要被移除的元素,將移除位置之後的元素向前挪動一個位置,
將list末尾元素置空(null),返回被移除的元素。

remove(Object o):
// 移除此列表中首次出現的指定元素(如果存在)。這是應為ArrayList中允許存放重複的元素。  
	public boolean remove(Object o) {  
		// 由於ArrayList中允許存放null,因此下面通過兩種情況來分別處理。  
		if (o == null) {  
			for (int index = 0; index < size; index++)  
				if (elementData[index] == null) {  
					// 類似remove(int index),移除列表中指定位置上的元素。  
					fastRemove(index);  
					return true;  
				}  
		} else {  
			for (int index = 0; index < size; index++)  
				if (o.equals(elementData[index])) {  
					fastRemove(index);  
					return true;  
				}  
			}  
			return false;  
		} 
	}
remove(Object o)中通過遍歷element尋找是否存在傳入物件,一旦找到就呼叫fastRemove移除物件。
fastRemove跳過了判斷邊界的處理,因為找到元素就相當於確定了index不會超過邊界,而且fastRemove並不返回被移除的元素。
	private void fastRemove(int index) {  
			 modCount++;  
			 int numMoved = size - index - 1;  
			 if (numMoved > 0)  
				 System.arraycopy(elementData, index+1, elementData, index,  
								  numMoved);  
			 elementData[--size] = null; // Let gc do its work  
	 }


	removeRange(int fromIndex,int toIndex):
	protected void removeRange(int fromIndex, int toIndex) {  
		 modCount++;  
		 int numMoved = size - toIndex;  
			 System.arraycopy(elementData, toIndex, elementData, fromIndex,  
							  numMoved);  
	   
		 // Let gc do its work  
		 int newSize = size - (toIndex-fromIndex);  
		 while (size != newSize)  
			 elementData[--size] = null;  
	}
執行過程是將elementData從toIndex位置開始的元素向前移動到fromIndex,然後將toIndex位置之後的元素全部置空順便修改size。


7、 調整陣列容量ensureCapacity: 
每當向陣列中新增元素時,都要去檢查新增後元素的個數是否會超出當前陣列的長度,如果超出,陣列將會進行擴容,以滿足新增資料的需求。
陣列擴容通過一個公開的方法ensureCapacity(int minCapacity)來實現。
在實際新增大量元素前,我也可以使用ensureCapacity來手動增加ArrayList例項的容量,以減少遞增式再分配的數量。
	public void ensureCapacity(int minCapacity) {  
		modCount++;  
		int oldCapacity = elementData.length;  
		if (minCapacity > oldCapacity) {  
			Object oldData[] = elementData;  
			int newCapacity = (oldCapacity * 3)/2 + 1;  //增加50%+1
				if (newCapacity < minCapacity)  
					newCapacity = minCapacity;  
		  // minCapacity is usually close to size, so this is a win:  
		  elementData = Arrays.copyOf(elementData, newCapacity);  
		}  
	 }

陣列進行擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量的增長大約是其原容量的1.5倍+1。
這種操作的代價是很高的,因此在實際使用時,我們應該儘量避免陣列容量的擴張。當我們可預知要儲存的元素的多少時,要在構造ArrayList例項時,就指定其容量,以避免陣列擴容的發生。
或者根據實際需求,通過呼叫ensureCapacity方法來手動增加ArrayList例項的容量。
Arrays.copyOf的實現時新建立了newCapacity大小的記憶體,然後把老的elementData放入。

8、關於ArrayList和Vector區別如下:
ArrayList在記憶體不夠時預設是擴充套件50% + 1個,Vector是預設擴充套件1倍。
Vector提供indexOf(obj, start)介面,ArrayList沒有。
Vector屬於執行緒安全級別的,但是大多數情況下不使用Vector,因為執行緒安全需要更大的系統開銷。

9、ArrayList還給我們提供了將底層陣列的容量調整為當前列表儲存的實際元素的大小的功能。
它可以通過trimToSize方法來實現。
public void trimToSize() {  
  modCount++;  
  int oldCapacity = elementData.length;  
  if (size < oldCapacity) {  
  elementData = Arrays.copyOf(elementData, size);  
  }  
}
由於elementData的長度會被拓展,size標記的是其中包含的元素的個數。所以會出現size很小但elementData.length很大的情況,將出現空間的浪費。
trimToSize將返回一個新的陣列給elementData,元素內容保持不變,length和size相同,節省空間。

10、轉為靜態陣列toArray
ArrayList的兩個轉化為靜態陣列的toArray方法。
第一個:呼叫Arrays.copyOf將返回一個數組,陣列內容是size個elementData的元素,即拷貝elementData從0至size-1位置的元素到新陣列並返回。

public Object[] toArray() {  
        return Arrays.copyOf(elementData, size);  


第二個:如果傳入陣列的長度小於size,返回一個新的陣列,大小為size,型別與傳入陣列相同。
所傳入陣列長度與size相等,則將elementData複製到傳入陣列中並返回傳入的陣列。若傳入陣列長度大於size,除了複製elementData外,還將把返回陣列的第size個元素置為空。

public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}


11、Fail-Fast機制:
ArrayList也採用了快速失敗的機制,通過記錄modCount引數來實現。在面對併發的修改時,迭代器很快就會完全失敗,而不是冒著在將來某個不確定時間發生任意不確定行為的風險。

12、總結:
1、注意其三個不同的構造方法。無參構造方法構造的ArrayList的容量預設為10,
帶有Collection引數的構造方法,將Collection轉化為陣列賦給ArrayList的實現陣列elementData。
2、注意擴充容量的方法ensureCapacity。
ArrayList在每次增加元素(可能是1個,也可能是一組)時,都要呼叫該方法來確保足夠的容量。
當容量不足以容納當前的元素個數時,就設定新的容量為舊的容量的1.5倍加1,如果設定後的新容量還不夠,
則直接新容量設定為傳入的引數(也就是所需的容量),而後用Arrays.copyof()方法將元素拷貝到新的陣列(詳見下面的第3點)。
從中可以看出,當容量不夠時,每次增加元素,都要將原來的元素拷貝到一個新的陣列中,非常之耗時,也因此建議在事先能確定元素數量的情況下,才使用ArrayList,否則建議使用LinkedList。
3、ArrayList的實現中大量地呼叫了Arrays.copyof()和System.arraycopy()方法。
Arrays.copyof()該方法實際上是在其內部又建立了一個長度為newlength的陣列,呼叫System.arraycopy()方法,將原來陣列中的元素複製到了新的陣列中。
System.arraycopy()方法。該方法被標記了native,呼叫了系統的C/C++程式碼,在JDK中是看不到的,但在openJDK中可以看到其原始碼。 該函式實際上最終呼叫了C語言的memmove()函式,因此它可以保證同一個陣列內元素的正確複製和移動,比一般的複製方法的實現效率要高很多,很適合用來批量處理陣列。 Java強烈推薦在複製大量陣列元素時用該方法,以取得更高的效率。
4、ArrayList基於陣列實現,可以通過下標索引直接查詢到指定位置的元素,因此查詢效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
5、在查詢給定元素索引值等的方法中,原始碼都將該元素的值分為null和不為null兩種情況處理,ArrayList中允許元素為null。











參考資料:
JDK API ArrayList
ArrayList 原始碼 
java原始碼分析之ArrayList
https://www.cnblogs.com/ITtangtang/p/3948555.html


每天努力一點,每天都在進步。