ArrayList原始碼剖析
1 //預設的初始陣列容量(最大長度) 2 private static final int DEFAULT_CAPACITY = 10; 3 //當陣列容量為0的時候的預設陣列(為什麼要宣告為static) 4 private static final Object[] EMPTY_ELEMENTDATA = {}; 5 //當陣列容量為預設容量時候的預設陣列,防止浪費記憶體空間,所以初始為空,當新增元素時根據預設容量和最少需要的容量哪個大來得到實際容量並生成新的陣列。 6 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};7 //ArrayList實際的資料結構形式,一個Object陣列 8 transient Object[] elementData; 9 //ArrayList的大小 10 private int size;
ArrayList提供了三種構造器。與LinkedList一樣,ArrayList也有無參構造器和引數為集合的構造器,同時ArrayList也提供了引數為整形的構造器,下面是這三個構造器的原始碼分析。
首先是帶有整形引數的構造器,引數表示初始容量。這段程式碼的邏輯比較簡單,判斷初始容量引數是否大於等於0,如果小於0則丟擲非法引數異常,如果等於0,則當前物件的陣列引用靜態的空陣列,如果大於0,則new一個容量為初始容量引數的Object陣列,完成初始化。
1 public ArrayList(int initialCapacity) { 2 if (initialCapacity > 0) { 3 this.elementData = new Object[initialCapacity]; 4 } else if (initialCapacity == 0) { 5 this.elementData = EMPTY_ELEMENTDATA; 6 } else { 7 throw new IllegalArgumentException("Illegal Capacity: "+ 8 initialCapacity); 9 } 10 }
其次是無參的構造器。我們會驚訝的發現,無參構造器並不是直接new一個容量為預設容量的陣列,而是引用了靜態的預設容量空陣列,之所以這麼做我想是因為,由無參構造器產生的ArrayList物件不一定會有元素在其中,為了節省記憶體空間,先使用靜態的預設容量空陣列,當新增元素的時候再判斷是否進行對elementData進行擴容。
1 public ArrayList() { 2 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 3 }
最後是引數為集合的構造器。泛型的extends保證了集合引數的泛型是當前ArrayList的泛型的類或者其子類。這段程式碼說明,集合引數通過toArray方法轉換為Object陣列,之後根據集合引數的大小,如果等於0,則elementData引用靜態的空陣列,否則再判斷elementData是否是Object[]型別(為什麼?),如果不是Object[]型別,則呼叫copyOf方法以Object[]物件的形式複製會elementData
1 public ArrayList(Collection<? extends E> c) { 2 elementData = c.toArray(); 3 if ((size = elementData.length) != 0) { 4 // c.toArray might (incorrectly) not return Object[] (see 6260652) 5 if (elementData.getClass() != Object[].class) 6 elementData = Arrays.copyOf(elementData, size, Object[].class); 7 } else { 8 // replace with empty array. 9 this.elementData = EMPTY_ELEMENTDATA; 10 } 11 }
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 } 6 7 private void grow(int minCapacity) { 8 // overflow-conscious code 9 int oldCapacity = elementData.length; 10 int newCapacity = oldCapacity + (oldCapacity >> 1); 11 if (newCapacity - minCapacity < 0) 12 newCapacity = minCapacity; 13 if (newCapacity - MAX_ARRAY_SIZE > 0) 14 newCapacity = hugeCapacity(minCapacity); 15 // minCapacity is usually close to size, so this is a win: 16 elementData = Arrays.copyOf(elementData, newCapacity); 17 }
add(ine index, E element)方法是將元素插入到指定的下標Index處。首先先檢查index是否合法,之後跟add(E e)一樣,確保陣列容量足夠,不同的地方在於,如果是在特定下標index處新增元素,則原本的[index, size-1]下標的元素要全部向後移一個位置,即對應的移動到[index + 1, size' - 1],注意size'=size + 1。
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); 3 4 ensureCapacityInternal(size + 1); // Increments modCount!! 5 System.arraycopy(elementData, index, elementData, index + 1, 6 size - index); 7 elementData[index] = element; 8 size++; 9 }
1 public E remove(int index) { 2 rangeCheck(index); 3 4 modCount++; 5 //之所以需要elementData(int index)方法來讀取特定下標元素,是因為讀操作和寫操作不同。ArrayList的底層是Object陣列,寫操作是絕對安全的(所有的類父類都是Object,所以Object可以引用任何類)。但是讀操作不同,讀取返回的型別是泛型E,無法確保Object到E的型別轉換是安全的,這時候編譯器就會發出警告。如果在每一個需要讀取元素的地方都是用element[index]的方式獲取元素,則編譯器會發出大量的警告,不方便我們除錯並且看著心煩,如果我們將element[index]封裝到一個方法裡,在方法裡進行強制型別轉換,並且用 @SuppressWarnings("unchecked")讓編譯器閉嘴,則會更美觀些哈哈。 6 E oldValue = elementData(index); 7 8 int numMoved = size - index - 1; 9 if (numMoved > 0) 10 System.arraycopy(elementData, index+1, elementData, index, 11 numMoved); 12 elementData[--size] = null; // clear to let GC do its work 13 14 return oldValue; 15 }
remove(Object o)方法與根據下標刪除的方法不同,其刪除之後不會將被刪除的元素返回。這個方法從前向後遍歷陣列,刪除遇到的第一個等於引數o的元素,這裡的等於形式的說法是這樣的,(o==null)?get(i)==null:o.equals(get(i))。
1 public boolean remove(Object o) { 2 if (o == null) { 3 for (int index = 0; index < size; index++) 4 if (elementData[index] == null) { 5 //fastRemove與普通remove不同之處在於其不進行邊界檢查,因為迴圈是從0到size-1,肯定不會刪到邊界之外的元素。 6 fastRemove(index); 7 return true; 8 } 9 } else { 10 for (int index = 0; index < size; index++) 11 if (o.equals(elementData[index])) { 12 fastRemove(index); 13 return true; 14 } 15 } 16 return false; 17 }
1 public E set(int index, E element) { 2 //下標檢查, 如果index >= size,則報IndexOutOfBoundsException。為什麼不檢查index是否為負數呢?因為如果index為負數,則之後的陣列訪問操作必然會丟擲ArrayIndexOutOfBoundsException,沒必要重複編碼來拋這個異常。 3 //IndexOutOfBoundsException和ArrayIndexOutOfBoundsException的關係和區別是,IndexOutOfBoundsException是發生於例如字串,陣列,以及其他集合在下標超出範圍的時候,是ArrayIndexOutOfBoundsException的父類,ArrayIndexOutOfBoundsException是其具體實現,只針對於訪問陣列的下標為負數或者大於等於陣列大小的情況。 4 rangeCheck(index); 5 6 E oldValue = elementData(index); 7 elementData[index] = element; 8 return oldValue; 9 }