ArraylList的擴容機制和使用ensureCapacity()方法提高性能

ArrayList的擴容規則是變成原來最大容量的1.5倍+1

具體爲什麼,現在看一下源碼:

[java] view plain copy
  1. public boolean add(E e) {  
  2.        ensureCapacityInternal(size + 1);  // Increments modCount!!  
  3.        elementData[size++] = e;  
  4.        return true;  
  5.    }  
ensureCapacityInternal是判斷是否要擴容的方法,下面是源碼:

[java] view plain copy
  1. private void ensureCapacityInternal(int minCapacity) {  
  2.         if (elementData == EMPTY_ELEMENTDATA) {  
  3.             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);  
  4.         }  
  5.   
  6.         ensureExplicitCapacity(minCapacity);  
  7.     }  

[java] view plain copy
  1. private transient Object[] elementData;  
elementData是存放元素的對象數組

[java] view plain copy
  1. private static final Object[] EMPTY_ELEMENTDATA = {};  
EMPTY_ELEMENTDATA是空數組,表示現在ArrayList是空的

ensureCapacityInternal中首先是判斷現在的ArrayList是不是空的,如果是空的,minCapacity就取默認的容量和傳入的參數minCapacity中的大值

然後調用ensureExplicitCapacity方法,

[java] view plain copy
  1. private void ensureExplicitCapacity(int minCapacity) {  
  2.       modCount++;  
  3.   
  4.       // overflow-conscious code  
  5.       if (minCapacity - elementData.length > 0)  
  6.           grow(minCapacity);  
  7.   }  

modCount是fail fast機制,在jdk1.6之前都是有volatile來修飾的,儘可能的讓併發訪問非安全的集合對象時儘快的失敗拋出異常,讓程序員修改代碼。

在jdk1.7中去掉了volatile修飾,因爲感覺沒有必要爲非線程安全集合浪費效率,在jdk1.5開始就提供了線程安全的集合類,在多線程環境下就應該使用線程安全的集合。

接着看,如果minCapacity的值大於add數據之前的大小,就調用grow方法,進行擴容,否則什麼也不做。

下面是具體看出來擴容機制的大小增長規則了:

[java] view plain copy
  1. private void grow(int minCapacity) {  
  2.        // overflow-conscious code  
  3.        int oldCapacity = elementData.length;  
  4.        int newCapacity = oldCapacity + (oldCapacity >> 1);  
  5.        if (newCapacity - minCapacity < 0)  
  6.            newCapacity = minCapacity;  
  7.        if (newCapacity - MAX_ARRAY_SIZE > 0)  
  8.            newCapacity = hugeCapacity(minCapacity);  
  9.        // minCapacity is usually close to size, so this is a win:  
  10.        elementData = Arrays.copyOf(elementData, newCapacity);  
  11.    }  
注意:這裏傳過來的minCapcatiy的值是size+1,能夠實現grow方法調用就肯定是(size+1)>elementData.length的情況,所以size就是初始最大容量或上一次擴容後達到的最大容量,所以纔會進行擴容。

newCapacity=oldCapacity+(oldCapacity>>1),這裏就是擴容大小確定的地方,相當於新的最大容量是 size+1+size/2 相當於原來的1.5倍然後加1

==============================================================================================================================

這樣每次size達到容量後,再插入數據就會造成擴容,默認的容量大小是10,如果我們現在連續插入17的對象,就會擴容兩次,第一次是在插入第11個對象時,第二次是在插入第17個對象時擴容,這樣會頻繁進行數組的拷貝,效率影響很大。那麼我們在插入數據數量已知的情況,可以調用ensureCapacity方法。

下面是方法源碼:

[java] view plain copy
  1. public void ensureCapacity(int minCapacity) {  
  2.         int minExpand = (elementData != EMPTY_ELEMENTDATA)  
  3.             // any size if real element table  
  4.             ? 0  
  5.             // larger than default for empty table. It's already supposed to be  
  6.             // at default size.  
  7.             : DEFAULT_CAPACITY;  
  8.   
  9.         if (minCapacity > minExpand) {  
  10.             ensureExplicitCapacity(minCapacity);  
  11.         }  
  12.     }  
爲什麼要先判斷先判斷elementData!=EMPTY_ELEMENTDATA呢?現在看一下ArrayList的無參構造函數

[java] view plain copy
  1. public ArrayList() {  
  2.        super();  
  3.        this.elementData = EMPTY_ELEMENTDATA;  
  4.    }  
這裏看出來ArrayList在沒有指定初始化容量的時候elementData就是一個空對象數組,沒有任何對象元素,也就是容量爲0,沒有分配空間。

可以再看一下add方法中,調用的ensureCapacityInternal方法中判斷elementData == EMPTY_ELEMENTDATA 是否爲空,如果爲空,minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);,minCapacity設置爲其中的大值,在默認是空的時候,初次調用add方法傳入的參數是1,也就是這裏的minCapacity就是1,現在minCapcity變成了DEFAULT_CAPACITY 也就是10,下面會調用grow方法,然後執行 elementData = Arrays.copyOf(elementData, 10);下面注意拷貝方法:

[java] view plain copy
  1. public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  
  2.        T[] copy = ((Object)newType == (Object)Object[].class)  
  3.            ? (T[]) new Object[newLength]  
  4.            : (T[]) Array.newInstance(newType.getComponentType(), newLength);  
  5.        System.arraycopy(original, 0, copy, 0,  
  6.                         Math.min(original.length, newLength));  
  7.        return copy;  
  8.    }  
執行了第一條語句後,copy=new Objecet[10]了,返回的結果數組大小就是10了,ArrayList相當於在沒指定initialCapacity時就是會使用延遲分配對象數組空間,當第一次插入元素時才分配10個對象空間。

上面分析過程也包含了擴容的步驟了。

當我們已經確定了要插入的對象的數目(並不是在創建ArrayList之前就知道有多少對象要插入的情況),就應該首先調用ensureCapacity來一次性擴容到可以容得下要插入的對象個數,這樣就避免的多次進行數組拷貝的情況,提高了效率,算是優化吧
當然,我們在創建ArrayList之前就知道有多少對象要插入就使用有參構造。

發佈了20 篇原創文章 · 獲贊 14 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章