簡介
來源:博客園 作者:吾王彥
博客鏈接:https://www.cnblogs.com/qinjunlin/p/13724987.html
ArrayList動態數組,是 java 中比較常用的數據結構。繼承自 AbstractList,實現了 List 接口。底層基於數組實現容量大小動態變化。本隨筆主要講述ArrayList的擴容機制以及它的底層實現。如果懶得看整個分析過程,你可以直接查看文章最后的總結。
成員變量
1 private static final int DEFAULT_CAPACITY = 10; //默認的初始容量為10 2 3 private static final Object[] EMPTY_ELEMENTDATA = {}; //空數組 4 5 /** 6 *與默認的初始容量DEFAULT_CAPACITY區分開來, 7 *簡單來講就是第一次添加元素時知道該 elementData 8 *從空的構造函數還是有參構造函數被初始化的。以便確認如何擴容。 9 **/ 10 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空數組,與EMPTY_ELEMENTDATA空數組區分 11 12 /** 13 * 存儲ArrayList元素的數組緩沖區。 14 * ArrayList的容量是這個數組緩沖區的長度。 15 *任何空的ArrayList 即 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 16 *當添加第一個元素時,將擴展為DEFAULT_CAPACITY。 17 **/ 18 transient Object[] elementData; 19 20 private int size; //實際元素個數
構造函數
有參構造
1 /** 2 * 構造一個初始容量為10的空ArrayList 3 */ 4 public ArrayList() { 5 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 6 }
這里需要注意,注釋(是我翻譯后的)是說構造一個容量大小為 10 的空的 list 數組,但這里構造函數了只是給 elementData 賦值了一個空的數組,實際上並未開始擴容,這時候的容量還是0,真正開始擴容其實是在第一次添加元素時才容量擴大至 10 的。
有參構造函數
下面的代碼是構造一個大小為 initialCapacity 的 ArrayList
1 public ArrayList(int initialCapacity) { 2 if (initialCapacity > 0) { //當 initialCapacity 大於零時初始化一個大小為 initialCapacity 的 object 數組並賦值給 elementData 3 this.elementData = new Object[initialCapacity]; 4 } else if (initialCapacity == 0) { //當 initialCapacity 為零時則是把 空數組EMPTY_ELEMENTDATA 賦值給 elementData 5 this.elementData = EMPTY_ELEMENTDATA; 6 } else { 7 throw new IllegalArgumentException("Illegal Capacity: "+ 8 initialCapacity); 9 } 10 }
使用指定 Collection 來構造 ArrayList 的構造函數。將 Collection 轉化為數組並賦值給 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) //判斷 elementData 的 class 類型是否為 Object[],不是的話則做一次轉換 6 elementData = Arrays.copyOf(elementData, size, Object[].class);// 7 } else { 8 // size 為零,則將空數組賦給elementData,相當於new ArrayList(0) 9 this.elementData = EMPTY_ELEMENTDATA; 10 } 11 }
產生擴容的操作
add方法
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); //每次添加元素到集合中時都會先確認下集合容量大小 3 elementData[size++] = e; //size 自增 +1。 4 return true; 5 } 6 7 //確認容量大小 8 private void ensureCapacityInternal(int minCapacity) { 9 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 10 } 11 12 //返回容量大小 13 private static int calculateCapacity(Object[] elementData, int minCapacity) { 14 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 15 return Math.max(DEFAULT_CAPACITY, minCapacity); //這里其實才是真正的開始初始化數組的容量大小為10 16 } 17 return minCapacity; 18 } 19 20 //根據確認的容量的大小判斷是否進行擴容 21 private void ensureExplicitCapacity(int minCapacity) { 22 modCount++; //記錄操作次數 23 24 // overflow-conscious code 25 if (minCapacity - elementData.length > 0) 26 grow(minCapacity); //調用擴容方法 27 }
由上面的源代碼可知數組的容量大小的初始化是在第一次添加元素的時候才開始的。calculateCapacity方法中有一個判斷elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,在第一次添加元素的時候,集合本身就是空的,所以會 就取 DEFAULT_CAPACITY 和 minCapacity 的最大值也就是 10。
初始化完容量后,之后的每次添加元素都會走一遍上面代碼的流程,就是每次添加元素前都會確認容量,然后根據minCapacity是否大於 elementData 的長度來決定是否對集合進行擴容。
grow擴容
1 //根據確認的容量的大小判斷是否進行擴容 2 private void ensureExplicitCapacity(int minCapacity) { 3 modCount++; //記錄操作次數 4 5 // overflow-conscious code 6 if (minCapacity - elementData.length > 0) 7 grow(minCapacity); //調用擴容方法 8 } 9 10 //擴容函數 11 private void grow(int minCapacity) { 12 // overflow-conscious code 13 int oldCapacity = elementData.length; 14 int newCapacity = oldCapacity + (oldCapacity >> 1); //這里NewCapacity為原來容量的1.5倍 15 if (newCapacity - minCapacity < 0) 16 newCapacity = minCapacity; 17 if (newCapacity - MAX_ARRAY_SIZE > 0) 18 newCapacity = hugeCapacity(minCapacity); 19 // minCapacity is usually close to size, so this is a win: 20 elementData = Arrays.copyOf(elementData, newCapacity); 21 }
1 //擴容過大,或者所需容量過大則調用此方法 2 private static int hugeCapacity(int minCapacity) { 3 if (minCapacity < 0) // overflow 4 throw new OutOfMemoryError(); 5 return (minCapacity > MAX_ARRAY_SIZE) ? 6 Integer.MAX_VALUE : 7 MAX_ARRAY_SIZE; 8 }
有代碼可知,這里的擴容是擴容至原來容量的 1.5 倍。為什么是1.5倍呢?可以看看下面的這個
以無參構造為例。
int oldCapacity = elementData.length; //原容量為10,二進制表示為1010
int newCapacity = oldCapacity + (oldCapacity >> 1); //此處將原來的1010右移一位,變成101,即10進制的5
所以 newCapacity = 10 + 5,即是15 。所以1.5倍是這樣來的。
可以理解“>>”右移符號相當於除以2
其次,擴容1.5並不一定是最終的容量大小。因為還有兩個判斷。
1、如果1.5倍太小的話,則將我們所需的容量大小賦值給newCapacity
2、1.5倍太大或者我們需要的容量太大,則調用 hugeCapacity方法。
太大究竟是多大?請看下面
hugeCapacity方法。中的參數分析
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //ArrayList的成員變量
Integer.MAX_VALUE的大小為2的31次-1,即2147483647所以你說大不大?嘻嘻
總結
首先,定義一個ArrayList,常用的有兩種方式。一種是使用無參構造,即new ArrayList();另一種是使用有參構造,new ArrayList(6),new一個初始容量大小為6的ArrayList。
使用無參構造定義的ArrayList,在沒有添加元素之前,初始容量為0,只有第一次添加元素的時候才會初始化初始容量為10,每次添加元素前都會確認集合的容量大小
如果容量不夠用會進行擴容。一般會擴容至原來的1.5倍,如果所需容量太大,會擴容到2的31次-1,或者(2的31次-1)-8(詳細看上面代碼分析)。
使用有參構造定義ArrayList,初始容量大小就是有參定義的大小,new ArrayList(6) ,初始容量就是6 。擴容機制有參無參構造都是一樣的。
看到這里,你是否還有個疑問,為什么是設計成擴容1.5倍呢?因為,因為他喜歡啊。哈哈哈哈哈。。。。。。。
JDK1.6 ArrayList無參構造,默認容量是10
JDK1.7 ArrayList無參構造,默認容量才改為是0,只有在add()時才初始化為10
這里討論的是JDK1.8的版本的