JDK1.8.131版本。java
首先查看ArrayList實例化方法相關代碼。數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private static final Object[] EMPTY_ELEMENTDATA = {}; private int size; public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList(Collection<? extends E> c) { 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; } }
默認無參構造方法。elementData實際存放數據元素,從這些能夠看出,ArrayList底層是用數組來實現,元素是連續的;這也體現了ArrayList有序,讀取快,固然數組維護角標,若是在ArrayList元素裏面隨機刪除個元素,就比較慢了。由於須要維護角標,因此被刪除元素後面元素的角標都須要-1。app
咱們知道ArrayList底層是使用數組來實現的,可是數組是不可變;ArrayList是怎麼實現,動態改變數據元素的呢?這個經過示例(查看源碼)來講明問題以及緣由。dom
ArrayList<String> list = new ArrayList<String>(); list.add("a");
使用ArrayList的無參構造方法,初始化list集合,並使用add方法添加一個"a"元素。ide
首先,ArrayList先將內部的 elementData 初始化(elementData = {}),這個時候數組的長度是0,list集合的長度也是0.測試
而後,add方法添加元素,方法中先執行ensureCapacityInternal(size + 1)方法。this
add方法以及其涉及到的方法:code
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * Increments modCount!! */ private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; 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); } /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
在ArrayList將元素添加到集合中以前,先執行了ensureCapacityInternal方法,執行這個方法的目的是檢查集合中元素(數組)是否是還能再插入一個元素,不能則擴容。ci
流程以下:element
先檢查elementData數組是不是默認初始化的數組,若是是則選擇默認初始化容量和最小容量數值大的那一個,再將數值賦值給minCapacity變量;若是elementData.lengh小於minCapacity,則擴容(grow).每次擴容後的容量爲int newCapacity = oldCapacity + (oldCapacity >> 1);(以前容量的1.5倍)。若是newCapacity(擴容後的容量)還小於(minCapacity),則直接將新容量定義爲minCapacity;若是newCapacity大於了MAX_ARRAY_SIZE,則比較minCapacity和MAX_ARRAY_SIZE的大小,大於則返回Integer.MAX_VALUE,不然返回MAX_ARRAY_SIZE,到此擴容後的數組長度肯定了,接着使用Arrays.copyOf方法,將以前元素,放入新的數組中,最後執行add方法中elementData[size++] = e,將元素放到集合中。至此添加元素流程完成。
自頂一個ZArrayList,來模擬ArrayList。
public class ZArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final int DEFAULT_CAPACITY = 10; private int size; public ZArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ZArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ZArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } } public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); } private int count = 0; private void grow(int minCapacity) { count +=1; int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } public int getCount() { return count; } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } @Override public E get(int index) { return null; } @Override public int size() { return 0; } }
首先定義全局變量int count = 0,在擴容方法grow中,使用count+=1,這樣能大體得出擴容次數。測試代碼
@Test public void testCountList(){ ZArrayList<Integer> zl = new ZArrayList<>(); System.out.println("初始化:"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); for (int a=0;a< 1000000;a++){ zl.add(a); log(zl, a); } System.out.println("---------------------"); for (int b = 1000000; b>0;b--){ zl.remove(zl.get(0)); log(zl, b); } } private void log(ZArrayList<Integer> zl, int b) { if (b == 1){ System.out.println("1次:\t"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); } else if (b==100){ System.out.println("100次:\t"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); } else if (b == 1000){ System.out.println("1000次:\t"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); } else if (b == 10000){ System.out.println("1萬次:\t"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); } else if (b == 100000){ System.out.println("10萬次:\t"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); } else if (b == 1000000){ System.out.println("100萬次:\t"+zl.getCount()+"\t內部數組長度:"+zl.elementData.length); } }
結果以下:
初始化:0 內部數組長度:0 1次: 1 內部數組長度:10 100次: 7 內部數組長度:109 1000次: 13 內部數組長度:1234 1萬次: 19 內部數組長度:14053 10萬次: 24 內部數組長度:106710 --------------------- 100萬次: 30 內部數組長度:1215487 10萬次: 30 內部數組長度:1215487 1萬次: 30 內部數組長度:1215487 1000次: 30 內部數組長度:1215487 100次: 30 內部數組長度:1215487 1次: 30 內部數組長度:1215487
從這些數據能夠得出若是依次添加100個元素,大概會擴容7次,若是將數據依次刪除,可是list內部數組的長度不變。
特別注意:ArrayList內部數組Object[] elementData的長度不等於ArrayList的size,經過源碼能夠看到在add/addAll/remove/removeAll中都維護了一個size變量,這個纔是ArrayList元素的個數。
從上邊的分析中能夠得出,若是添加數據量比較大的話,最好是給ArrayList一個默認的初始容量,用以減小擴容的次數。
以此類推ArrayList的其餘方法addAll和remove方法,原理是類似的,再也不贅述。