從 ArrayList 的構造函數說起
ArrayList有三種方式來初始化,構造方法源碼如下:
/**? ? * 默認初始容量大小*/privatestaticfinalintDEFAULT_CAPACITY=10;privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};/**? ? *默認構造函數,使用初始容量10構造一個空列表(無參數構造)*/publicArrayList() {this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;? ? }/**? ? * 帶初始容量參數的構造函數。(用戶自己指定容量)*/publicArrayList(intinitialCapacity) {if(initialCapacity>0) {//初始容量大于0//創建initialCapacity大小的數組this.elementData=newObject[initialCapacity];? ? ? ? }elseif(initialCapacity==0) {//初始容量等于0//創建空數組this.elementData=EMPTY_ELEMENTDATA;? ? ? ? }else{//初始容量小于0,拋出異常thrownewIllegalArgumentException("Illegal Capacity:"+initialCapacity);? ? ? ? }? ? }/**? ? *構造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回? ? *如果指定的集合為null,throws NullPointerException。 */publicArrayList(Collectionc) {? ? ? ? elementData=c.toArray();if((size=elementData.length)!=0) {//c.toArray might (incorrectly) not return Object[] (see 6260652)if(elementData.getClass()!=Object[].class)? ? ? ? ? ? ? ? elementData=Arrays.copyOf(elementData, size,Object[].class);? ? ? ? }else{//replace with empty array.this.elementData=EMPTY_ELEMENTDATA;? ? ? ? }? ? }
細心的同學一定會發現 :以無參數構造方法創建 ArrayList 時,實際上初始化賦值的是一個空數組。當真正對數組進行添加元素操作時,才真正分配容量。即向數組中添加第一個元素時,數組容量擴為10。?下面在我們分析 ArrayList 擴容時會降到這一點內容!
這里以無參構造函數創建的 ArrayList 為例分析
/**? ? * 將指定的元素追加到此列表的末尾。 */publicbooleanadd(Ee) {//添加元素之前,先調用ensureCapacityInternal方法ensureCapacityInternal(size+1);//Increments modCount!!//這里看到ArrayList添加元素的實質就相當于為數組賦值elementData[size++]=e;returntrue;? ? }
2. 再來看看?ensureCapacityInternal()?方法
可以看到?add?方法 首先調用了ensureCapacityInternal(size + 1)
//得到最小擴容量privatevoidensureCapacityInternal(intminCapacity) {if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//獲取默認的容量和傳入參數的較大值minCapacity=Math.max(DEFAULT_CAPACITY, minCapacity);? ? ? ? }? ? ? ? ensureExplicitCapacity(minCapacity);? ? }
當 要 add 進第1個元素時,minCapacity為1,在Math.max()方法比較后,minCapacity 為10。
3.?ensureExplicitCapacity()?方法
如果調用?ensureCapacityInternal()?方法就一定會進過(執行)這個方法,下面我們來研究一下這個方法的源碼!
//判斷是否需要擴容privatevoidensureExplicitCapacity(intminCapacity) {? ? ? ? modCount++;//overflow-conscious codeif(minCapacity-elementData.length>0)//調用grow方法進行擴容,調用此方法代表已經開始擴容了grow(minCapacity);? ? }
我們來仔細分析一下:
當我們要 add 進第1個元素到 ArrayList 時,elementData.length 為0 (因為還是一個空的 list),因為執行了?ensureCapacityInternal()?方法 ,所以 minCapacity 此時為10。此時,minCapacity - elementData.length > 0?成立,所以會進入?grow(minCapacity)?方法。
當add第2個元素時,minCapacity 為2,此時e lementData.length(容量)在添加第一個元素后擴容成 10 了。此時,minCapacity - elementData.length > 0?不成立,所以不會進入 (執行)grow(minCapacity)?方法。
添加第3、4···到第10個元素時,依然不會執行grow方法,數組容量都為10。
直到添加第11個元素,minCapacity(為11)比elementData.length(為10)要大。進入grow方法進行擴容。
/**? ? * 要分配的最大數組大小*/privatestaticfinalintMAX_ARRAY_SIZE=Integer.MAX_VALUE-8;/**? ? * ArrayList擴容的核心方法。*/privatevoidgrow(intminCapacity) {//oldCapacity為舊容量,newCapacity為新容量intoldCapacity=elementData.length;//將oldCapacity 右移一位,其效果相當于oldCapacity /2,//我們知道位運算的速度遠遠快于整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍,intnewCapacity=oldCapacity+(oldCapacity>>1);//然后檢查新容量是否大于最小需要容量,若還是小于最小需要容量,那么就把最小需要容量當作數組的新容量,if(newCapacity-minCapacity<0)? ? ? ? ? ? newCapacity=minCapacity;//如果新容量大于 MAX_ARRAY_SIZE,進入(執行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE,//如果minCapacity大于最大容量,則新容量則為`Integer.MAX_VALUE`,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 `Integer.MAX_VALUE - 8`。if(newCapacity-MAX_ARRAY_SIZE>0)? ? ? ? ? ? newCapacity=hugeCapacity(minCapacity);//minCapacity is usually close to size, so this is a win:elementData=Arrays.copyOf(elementData, newCapacity);? ? }
int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次擴容之后容量都會變為原來的 1.5 倍!?記清楚了!不是網上很多人說的 1.5 倍+1!
">>"(移位運算符):>>1 右移一位相當于除2,右移n位相當于除以 2 的 n 次方。這里 oldCapacity 明顯右移了1位所以相當于oldCapacity /2。對于大數據的2進制運算,位移運算符比那些普通運算符的運算要快很多,因為程序僅僅移動一下而已,不去計算,這樣提高了效率,節省了資源
我們再來通過例子探究一下grow()?方法 :
當add第1個元素時,oldCapacity 為0,經比較后第一個if判斷成立,newCapacity = minCapacity(為10)。但是第二個if判斷不會成立,即newCapacity 不比 MAX_ARRAY_SIZE大,則不會進入?hugeCapacity?方法。數組容量為10,add方法中 return true,size增為1。
當add第11個元素進入grow方法時,newCapacity為15,比minCapacity(為11)大,第一個if判斷不成立。新容量沒有大于數組最大size,不會進入hugeCapacity方法。數組容量擴為15,add方法中return true,size增為11。
以此類推······
這里補充一點比較重要,但是容易被忽視掉的知識點:
java 中的?length?屬性是針對數組說的,比如說你聲明了一個數組,想知道這個數組的長度則用到了 length 這個屬性.
java 中的?length()?方法是針對字符串說的,如果想看這個字符串的長度則用到?length()?這個方法.
java 中的?size()?方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調用此方法來查看!
從上面?grow()?方法源碼我們知道: 如果新容量大于 MAX_ARRAY_SIZE,進入(執行)?hugeCapacity()?方法來比較 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,則新容量則為Integer.MAX_VALUE,否則,新容量大小則為 MAX_ARRAY_SIZE 即為?Integer.MAX_VALUE - 8。
privatestaticinthugeCapacity(intminCapacity) {if(minCapacity<0)//overflowthrownewOutOfMemoryError();//對minCapacity和MAX_ARRAY_SIZE進行比較//若minCapacity大,將Integer.MAX_VALUE作為新數組的大小//若MAX_ARRAY_SIZE大,將MAX_ARRAY_SIZE作為新數組的大小//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;return(minCapacity>MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;? ? }
三?System.arraycopy()?和?Arrays.copyOf()方法
閱讀源碼的話,我們就會發現 ArrayList 中大量調用了這兩個方法。比如:我們上面講的擴容操作以及add(int index, E element)、toArray()?等方法中都用到了該方法!
/**? ? * 在此列表中的指定位置插入指定的元素。? ? ? *先調用 rangeCheckForAdd 對index進行界限檢查;然后調用 ensureCapacityInternal 方法保證capacity足夠大;? ? *再將從index開始之后的所有成員后移一個位置;將element插入index位置;最后size加1。*/publicvoidadd(intindex,Eelement) {? ? ? ? rangeCheckForAdd(index);? ? ? ? ensureCapacityInternal(size+1);//Increments modCount!!//arraycopy()方法實現數組自己復制自己//elementData:源數組;index:源數組中的起始位置;elementData:目標數組;index + 1:目標數組中的起始位置; size - index:要復制的數組元素的數量;System.arraycopy(elementData, index, elementData, index+1, size-index);? ? ? ? elementData[index]=element;? ? ? ? size++;? ? }
我們寫一個簡單的方法測試以下:
publicclassArraycopyTest{publicstaticvoidmain(String[]args) {//TODO Auto-generated method stubint[] a=newint[10];a[0]=0;a[1]=1;a[2]=2;a[3]=3;System.arraycopy(a,2, a,3,3);a[2]=99;for(inti=0; i<a.length; i++) {System.out.println(a[i]);}}}
結果:
0 1 99 2 3 0 0 0 0 0
/**? ? 以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最后一個元素); 返回的數組的運行時類型是指定數組的運行時類型。 */publicObject[] toArray() {//elementData:要復制的數組;size:要復制的長度returnArrays.copyOf(elementData, size);? ? }
個人覺得使用?Arrays.copyOf()方法主要是為了給原有數組擴容,測試代碼如下:
publicclassArrayscopyOfTest{publicstaticvoidmain(String[]args) {int[] a=newint[3];a[0]=0;a[1]=1;a[2]=2;int[] b=Arrays.copyOf(a,10);System.out.println("b.length"+b.length);}}
結果:
10
聯系:
看兩者源代碼可以發現 copyOf() 內部實際調用了?System.arraycopy()?方法
區別:
arraycopy()?需要目標數組,將原數組拷貝到你自己定義的數組里或者原數組,而且可以選擇拷貝的起點和長度以及放入新數組中的位置?copyOf()?是系統自動在內部新建一個數組,并返回該數組。
ArrayList 源碼中有一個?ensureCapacity?方法不知道大家注意到沒有,這個方法 ArrayList 內部沒有被調用過,所以很顯然是提供給用戶調用的,那么這個方法有什么作用呢?
/**? ? 如有必要,增加此 ArrayList 實例的容量,以確保它至少可以容納由minimum capacity參數指定的元素數。? ? **@paramminCapacity? 所需的最小容量*/publicvoidensureCapacity(intminCapacity) {intminExpand=(elementData!=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)//any size if not default element table?0//larger than default for default empty table. It's already//supposed to be at default size.:DEFAULT_CAPACITY;if(minCapacity>minExpand) {? ? ? ? ? ? ensureExplicitCapacity(minCapacity);? ? ? ? }? ? }
最好在 add 大量元素之前用?ensureCapacity?方法,以減少增量從新分配的次數
我們通過下面的代碼實際測試以下這個方法的效果:
publicclassEnsureCapacityTest{publicstaticvoidmain(String[]args) {ArrayListlist=newArrayList();finalintN=10000000;longstartTime=System.currentTimeMillis();for(inti=0; i<N; i++) {list.add(i);}longendTime=System.currentTimeMillis();System.out.println("使用ensureCapacity方法前:"+(endTime-startTime));list=newArrayList();longstartTime1=System.currentTimeMillis();list.ensureCapacity(N);for(inti=0; i<N; i++) {list.add(i);}longendTime1=System.currentTimeMillis();System.out.println("使用ensureCapacity方法后:"+(endTime1-startTime1));}}
運行結果:
使用ensureCapacity方法前:4637
使用ensureCapacity方法后:241
通過運行結果,我們可以很明顯的看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity?方法,以減少增量從新分配的次數