Java集合篇:ArrayList详解
一、ArrayList概述:
ArrayList是實現了List接口的動態數組,所謂動態數組就是他的大小是可變的。實現了所有可選列表操作,并允許包括Null在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。
每個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。默認初始容量是10。默認初始容量為10。隨著ArrayList中元素的增加,它的容量也會不斷的自動增長。在每次添加元素時,ArrayList都會檢查是否需要進行擴容操作,擴容操作帶來數據向新數組的重新拷貝,所以如果我們知道具體業務數據量,在構造ArrayList時,可以給ArrayList 指定一個初始容量,這樣就會減少擴容時的拷貝問題。當然在添加大量元素前,應用程序也可以使用ensureCapacity操作來增加ArrayList實例的容量,這可以減少遞增式再分配的數量。
ArrayList 的實現不是同步的如果多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那么它必須保持外部同步。所以為了保證同步,最好的辦法是在創建時完成,以防止意外對列表進行不同步的訪問:
List list = Collections.synchronizedList(new ArrayList(...));
?
二、ArrayList源碼分析:
ArrayList是實現List接口的,底層采用數組實現,所以它的操作基本上都是基于對數組的操作。
1、底層使用數組:
private transient Object[] elementData;transient 為java關鍵字,為變量修飾符,如果用transient聲明一個實例變量,當對象存儲時,它的值不需要維持。Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。當一個對象被序列化的時候,transient型變量的值不包括在序列化的表示中,然而非transient型的變量是被包括進去的。?
這里Object[] elementData,就是我們的ArrayList容器,下面介紹的基本操作都是基于該elementData變量來進行操作的。
2、構造函數:
ArrayList提供了三個構造函數:
ArrayList():默認構造函數,提供初始容量為10的空列表。
ArrayList(int initialCapacity):構造一個具有指定初始容量的空列表。
ArrayList(Collection<? extends?E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。
/*** 構造一個初始容量為 10 的空列表*/public ArrayList() {this(10);}/*** 構造一個具有指定初始容量的空列表。*/public ArrayList(int initialCapacity) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);this.elementData = new Object[initialCapacity];}/*** 構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。*/public ArrayList(Collection<? extends E> c) {elementData = c.toArray();size = elementData.length;// c.toArray might (incorrectly) not return Object[] (see 6260652)if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);}3、新增:
?ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)這個五個方法來實現ArrayList增加。
3.1 add(E e):將指定的元素添加到此列表的尾部。
public boolean add(E e) {ensureCapacity(size + 1); // Increments modCount!!elementData[size++] = e;return true;}這里ensureCapacity()方法是對ArrayList集合進行擴容操作,elementData(size++) = e,將列表末尾元素指向e。
?3.2 add(int index, E element):將指定的元素插入此列表中的指定位置。
public void add(int index, E element) {//判斷索引位置是否正確if (index > size || index < 0)throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);//擴容檢測ensureCapacity(size+1); /** 對源數組進行復制處理(位移),從index + 1到size-index。* 主要目的就是空出index位置供數據插入,* 即向右移動當前位于該位置的元素以及所有后續元素。 */System.arraycopy(elementData, index, elementData, index + 1,size - index);//在指定位置賦值elementData[index] = element;size++;}在這個方法中最根本的方法就是System.arraycopy()方法,該方法的根本目的就是將index位置空出來以供新數據插入,這里需要進行數組數據的右移,這是非常麻煩和耗時的,所以如果指定的數據集合需要進行大量插入(中間插入)操作,推薦使用LinkedList。
? ? ? 3.3 addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素順序,將該 collection 中的所有元素添加到此列表的尾部。
public boolean addAll(Collection<? extends E> c) {// 將集合C轉換成數組Object[] a = c.toArray();int numNew = a.length;// 擴容處理,大小為size + numNewensureCapacity(size + numNew); // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}這個方法無非就是使用System.arraycopy()方法將C集合(先準換為數組)里面的數據復制到elementData數組中。這里就稍微介紹下System.arraycopy(),因為下面還將大量用到該方法。該方法的原型為:public static void?arraycopy(Object?src, int srcPos,?Object?dest, int destPos, int length)。它的根本目的就是進行數組元素的復制。即從指定源數組中復制一個數組,復制從指定的位置開始,到目標數組的指定位置結束。將源數組src從srcPos位置開始復制到dest數組中,復制長度為length,數據從dest的destPos位置開始粘貼。
3.4 addAll(int index, Collection<? extends E> c):從指定的位置開始,將指定 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;//ArrayList容器擴容處理ensureCapacity(size + numNew); // Increments modCount//ArrayList容器數組向右移動的位置int numMoved = size - index;//如果移動位置大于0,則將ArrayList容器的數據向右移動numMoved個位置,確保增加的數據能夠增加if (numMoved > 0)System.arraycopy(elementData, index, elementData, index + numNew,numMoved);//添加數組System.arraycopy(a, 0, elementData, index, numNew);//容器容量變大size += numNew; return numNew != 0;}3.5 set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
public E set(int index, E element) {//檢測插入的位置是否越界RangeCheck(index);E oldValue = (E) elementData[index];//替代elementData[index] = element;return oldValue;}4、刪除:
ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四個方法進行元素的刪除。
4.1 remove(int index):移除此列表中指定位置上的元素。
public E remove(int index) {//位置驗證RangeCheck(index);modCount++;//需要刪除的元素E oldValue = (E) elementData[index]; //向左移的位數int numMoved = size - index - 1;//若需要移動,則想左移動numMoved位if (numMoved > 0)System.arraycopy(elementData, index + 1, elementData, index,numMoved);//置空最后一個元素elementData[--size] = null; // Let gc do its workreturn oldValue;}4.2 remove(Object o):移除此列表中首次出現的指定元素(如果存在)。
public boolean remove(Object o) {//因為ArrayList中允許存在null,所以需要進行null判斷if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {//移除這個位置的元素fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;}其中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}4.3 removeRange(int fromIndex, int toIndex):移除列表中索引在?fromIndex(包括)和?toIndex(不包括)之間的所有元素。
protected void removeRange(int fromIndex, int toIndex) {modCount++;int numMoved = size - toIndex;System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);// Let gc do its workint newSize = size - (toIndex - fromIndex);while (size != newSize)elementData[--size] = null;}4.4 removeAll():是繼承自AbstractCollection的方法,ArrayList本身并沒有提供實現。
public boolean removeAll(Collection<?> c) {boolean modified = false;Iterator<?> e = iterator();while (e.hasNext()) {if (c.contains(e.next())) {e.remove();modified = true;}}return modified;}5、查找:
ArrayList提供了get(int index)用讀取ArrayList中的元素。由于ArrayList是動態數組,所以我們完全可以根據下標來獲取ArrayList中的元素,而且速度還比較快,故ArrayList長于隨機訪問。
public E get(int index) {RangeCheck(index);return (E) elementData[index];}6、擴容:
在上面的新增方法的源碼中我們發現每個方法中都存在這個方法:ensureCapacity(),該方法就是ArrayList的擴容方法。在前面就提過ArrayList每次新增元素時都會需要進行容量檢測判斷,若新增元素后元素的個數會超過ArrayList的容量,就會進行擴容操作來滿足新增元素的需求。所以當我們清楚知道業務數據量或者需要插入大量元素前,我可以使用ensureCapacity來手動增加ArrayList實例的容量,以減少遞增式再分配的數量。
public void ensureCapacity(int minCapacity) {//修改計時器modCount++;//ArrayList容量大小int oldCapacity = elementData.length;/** 若當前需要的長度大于當前數組的長度時,進行擴容操作*/if (minCapacity > oldCapacity) {Object oldData[] = elementData;//計算新的容量大小,為當前容量的1.5倍int newCapacity = (oldCapacity * 3) / 2 + 1;if (newCapacity < minCapacity)newCapacity = minCapacity;//數組拷貝,生成新的數組elementData = Arrays.copyOf(elementData, newCapacity);}}在這里有一個疑問,為什么每次擴容處理會是1.5倍,而不是2.5、3、4倍呢?通過google查找,發現1.5倍的擴容是最好的倍數。因為一次性擴容太大(例如2.5倍)可能會浪費更多的內存(1.5倍最多浪費33%,而2.5被最多會浪費60%,3.5倍則會浪費71%……)。但是一次性擴容太小,需要多次對數組重新分配內存,對性能消耗比較嚴重。所以1.5倍剛剛好,既能滿足性能需求,也不會造成很大的內存消耗。
處理這個ensureCapacity()這個擴容數組外,ArrayList還給我們提供了將底層數組的容量調整為當前列表保存的實際元素的大小的功能。它可以通過trimToSize()方法來實現。該方法可以最小化ArrayList實例的存儲量。
public void trimToSize() {modCount++;int oldCapacity = elementData.length;if (size < oldCapacity) {elementData = Arrays.copyOf(elementData, size);}}?
文章轉自:http://www.cnblogs.com/chenssy/p/3498468.html
?
?
總結
以上是生活随笔為你收集整理的Java集合篇:ArrayList详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java集合篇:Stack
- 下一篇: Java集合篇:Map总结