java-基础-ArrayList剖析
ArrayList是List接口的可變數組的實現。實現了所有可選列表操作,并允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。每個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它總是至少等于列表的大小。隨著向ArrayList中不斷添加元素,其容量也自動增長。自動增長會帶來數據向新數組的重新拷貝,因此,如果可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前,應用程序也可以使用ensureCapacity操作來增加ArrayList實例的容量,這可以減少遞增式再分配的數量。
注意,此實現不是同步的。如果多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那么它必須保持外部同步。
ArrayList是可以動態(tài)增長和縮減的索引序列,它是基于數組實現的List類。ArrayList是基于數組實現的,是一個動態(tài)數組,其容量能自動增長,類似于C語言中的動態(tài)申請內存,動態(tài)增長內存。 ArrayList不是線程安全的,只能用在單線程環(huán)境下,多線程環(huán)境下可以考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可以使用concurrent并發(fā)包下的CopyOnWriteArrayList類。
ArrayList實現了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實現了RandomAccess接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable接口,能被克隆。每個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它總是至少等于列表的大小。隨著向ArrayList中不斷添加元素,其容量也自動增長。自動增長會帶來數據向新數組的重新拷貝,因此,如果可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前,應用程序也可以使用ensureCapacity操作來增加ArrayList實例的容量,這可以減少遞增式再分配的數量。 如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以減少增加重分配的次數提高性能。
注意,此實現不是同步的。如果多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那么它必須保持外部同步。
ArrayList的用法和Vector向類似,但是Vector是一個較老的集合,具有很多缺點,不建議使用。另外,ArrayList和Vector的區(qū)別是:ArrayList是線程不安全的,當多條線程訪問同一個ArrayList集合時,程序需要手動保證該集合的同步性,而Vector則是線程安全的。
ArrayList定義只定義類兩個私有屬性
elementData存儲ArrayList內的元素,size表示它包含的元素的數量。transient。
Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
/** * 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;被標記為transient的屬性在對象被序列化的時候不會被保存。
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); } // 創(chuàng)建一個包含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); }ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection
// 用指定的元素替代此列表中指定位置上的元素,并返回以前位于該位置上的元素。 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; } // 將指定的元素插入此列表中的指定位置。 // 如果當前位置有元素,則向右移動當前位于該位置的元素以及所有后續(xù)元素(將其索引加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數組中。 // 即將當前位于該位置的元素以及所有后續(xù)元素右移一個位置。 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; }ArrayList是基于數組實現的,屬性中也看到了數組,具體是怎么實現的呢?比如就這個添加元素的方法,如果數組大,則在將某個位置的值設置為指定元素即可,如果數組容量不夠了呢?
看到add(E e)中先調用了ensureCapacity(size+1)方法,之后將元素的索引賦給elementData[size],而后size自增。例如初次添加時,size為0,add將elementData[0]賦值為e,然后size設置為1(類似執(zhí)行以下兩條語句elementData[0]=e;size=1)。將元素的索引賦給elementData[size]不是會出現數組越界的情況嗎?這里關鍵就在ensureCapacity(size+1)中了。 // 返回此列表中指定位置上的元素。 public E get(int index) { RangeCheck(index); return (E) elementData[index]; }ArrayList提供了根據下標或者指定對象兩種方式的刪除功能。如下:
// 移除此列表中指定位置上的元素。 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),返回被移除的元素。
// 移除此列表中首次出現的指定元素(如果存在)。這是應為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; } }首先通過代碼可以看到,當移除成功后返回true,否則返回false。remove(Object o)中通過遍歷element尋找是否存在傳入對象,一旦找到就調用fastRemove移除對象。為什么找到了元素就知道了index,不通過remove(index)來移除元素呢?因為fastRemove跳過了判斷邊界的處理,因為找到元素就相當于確定了index不會超過邊界,而且fastRemove并不返回被移除的元素。下面是fastRemove的代碼,基本和remove(index)一致。
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 } 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; }執(zhí)行過程是將elementData從toIndex位置開始的元素向前移動到fromIndex,然后將toIndex位置之后的元素全部置空順便修改size。
這個方法是protected,及受保護的方法,為什么這個方法被定義為protected呢
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; //增加50%+1if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中,每次數組容量的增長大約是其原容量的1.5倍。這種操作的代價是很高的,因此在實際使用時,我們應該盡量避免數組容量的擴張。當我們可預知要保存的元素的多少時,要在構造ArrayList實例時,就指定其容量,以避免數組擴容的發(fā)生。或者根據實際需求,通過調用ensureCapacity方法來手動增加ArrayList實例的容量。
Object oldData[] = elementData;//為什么要用到oldData[]
乍一看來后面并沒有用到關于oldData, 這句話顯得多此一舉!但是這是一個牽涉到內存管理的類, 所以要了解內部的問題。 而且為什么這一句還在if的內部,這跟elementData = Arrays.copyOf(elementData, newCapacity); 這句是有關系的,下面這句Arrays.copyOf的實現時新創(chuàng)建了newCapacity大小的內存,然后把老的elementData放入。好像也沒有用到oldData,有什么問題呢。問題就在于舊的內存的引用是elementData, elementData指向了新的內存塊,如果有一個局部變量oldData變量引用舊的內存塊的話,在copy的過程中就會比較安全,因為這樣證明這塊老的內存依然有引用,分配內存的時候就不會被侵占掉,然后copy完成后這個局部變量的生命期也過去了,然后釋放才是安全的。不然在copy的的時候萬一新的內存或其他線程的分配內存侵占了這塊老的內存,而copy還沒有結束,這將是個嚴重的事情。
關于ArrayList和Vector區(qū)別如下:
ArrayList在內存不夠時默認是擴展50% + 1個,Vector是默認擴展1倍。
Vector提供indexOf(obj, start)接口,ArrayList沒有。
Vector屬于線程安全級別的,但是大多數情況下不使用Vector,因為線程安全需要更大的系統(tǒng)開銷。
ArrayList還給我們提供了將底層數組的容量調整為當前列表保存的實際元素的大小的功能。它可以通過trimToSize方法來實現。代碼如下:
由于elementData的長度會被拓展,size標記的是其中包含的元素的個數。所以會出現size很小但elementData.length很大的情況,將出現空間的浪費。trimToSize將返回一個新的數組給elementData,元素內容保持不變,length和size相同,節(jié)省空間。
兩個轉化為靜態(tài)數組的toArray方法
第一個, 調用Arrays.copyOf將返回一個數組,數組內容是size個elementData的元素,即拷貝elementData從0至size-1位置的元素到新數組并返回。
第二個,如果傳入數組的長度小于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;}關于ArrayList的源碼,給出幾點比較重要的總結:
1、注意其三個不同的構造方法。無參構造方法構造的ArrayList的容量默認為10,帶有Collection參數的構造方法,將Collection轉化為數組賦給ArrayList的實現數組elementData。2、注意擴充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1個,也可能是一組)時,都要調用該方法來確保足夠的容量。當容量不足以容納當前的元素個數時,就設置新的容量為舊的容量的1.5倍加1,如果設置后的新容量還不夠,則直接新容量設置為傳入的參數(也就是所需的容量),而后用Arrays.copyof()方法將元素拷貝到新的數組(詳見下面的第3點)。從中可以看出,當容量不夠時,每次增加元素,都要將原來的元素拷貝到一個新的數組中,非常之耗時,也因此建議在事先能確定元素數量的情況下,才使用ArrayList,否則建議使用LinkedList。ArrayList的實現中大量地調用了Arrays.copyof()和System.arraycopy()方法。我們有必要對這兩個方法的實現做下深入的了解。
Arrays.copyof()方法。它有很多個重載的方法,但實現思路都是一樣的,我們來看泛型版本的源碼
很明顯調用了另一個copyof方法,該方法有三個參數,最后一個參數指明要轉換的數據的類型
這里可以很明顯地看出,該方法實際上是在其內部又創(chuàng)建了一個長度為newlength的數組,調用System.arraycopy()方法,將原來數組中的元素復制到了新的數組中。
下面來看System.arraycopy()方法。該方法被標記了native,調用了系統(tǒng)的C/C++代碼,在JDK中是看不到的,但在openJDK中可以看到其源碼。該函數實際上最終調用了C語言的memmove()函數,因此它可以保證同一個數組內元素的正確復制和移動,比一般的復制方法的實現效率要高很多,很適合用來批量處理數組。Java強烈推薦在復制大量數組元素時用該方法,以取得更高的效率。
ArrayList基于數組實現,可以通過下標索引直接查找到指定位置的元素,因此查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
在查找給定元素索引值等的方法中,源碼都將該元素的值分為null和不為null兩種情況處理,ArrayList中允許元素為null。
總結
以上是生活随笔為你收集整理的java-基础-ArrayList剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于float的几种布局
- 下一篇: SHA-1退休:数千万用户通向加密网站之