此文章分兩部分,1.ArrayList用法。2.源碼分析。先用法後分析是爲了之後忘了查閱起來方便~~數組
ArrayList 基本用法
1.建立ArrayList對象markdown
//建立默認容量的數組列表(默認爲10) ArrayList<E> a = new ArrayList<E>(); //建立容量爲initialCapacity的數組列表 ArrayList<E> a = new ArrayList<E>(int initialCapacity); //用一個集合來初始化數值列表 ArrayList<E> a = new ArrayList<E>(Collection<? extends E> c);
Capacity 與 Size 時兩個不一樣的概念:
Size: ArrayList包含的元素個數。
capacity : ArrayList的容量(默認爲10)。它是一個大於或等於size的值。當它的值小於size是就會對ArrayList進行擴容實現可變長數組。簡單來講,capacity就是爲了實現可變長數組而設計的。 (具體分析請看下面的源碼分析)數據結構
2.ArrayList方法 app
/***********a是上面建立的ArrayList對象***************/ //添加一個元素 a.add(E e); //在特定的位置添加元素,index後面的元素所有向後移一位 a.add(int index, E e); //在ArrayList 後面 添加集合 a.addAll(Collection<? extends E> c); //從特定的位置添加集合 a.addAll(int index, Collection<? extends E> c);
//獲取特定索引的元素 a.get(int index);
遍歷ArrayList: 通常使用for-each
for(E e : a)
dosomething;
用for-each的主要優點:
相對傳統的for循環,for-each更簡潔,不容易出錯,並且沒有性能的損失函數
//用e替換換index位置的元素 a.set(int index, E e);
注意: 區分set與add的用法
add 是添加一個新的元素,要開闢新的空間來保存元素
set 是修改特定位置的元素,index位置必需要已經存在元素,不然會越界 源碼分析
//刪除索引爲index的元素,後面的元素全都向前移一位 a.remove(int index); /*刪除在列表中和obj相等的第一個元素(首次出現), * 就作動做,保持列表原來的狀態 * 好比:a [1, 3, 7, 5, 7],執行a.remove((Integer)7)後 * 結果爲:[1, 3, 5, 7] */ a.remove(Object obj); //刪除與集合c中元素相等的元素 a.removeAll(Collection<?> c); //保留與集合c中元素相等的元素,而後把其餘元素刪除(與removeAll相反) a.retainAll(Collection<?> c); //刪除ArrayList的全部元素,沒有removeAll();方法 a.clear();
//獲取ArrayList的元素個數 a.size(); //判斷ArrayList是否包含某個元素 a.contains(Object o) //判斷ArrayList是不是空列表([]),是就返回true a.isEmpty(); //獲取ArrayList的子列表,(字串包含索引爲fromIndex的元素,不包含toIndex元素) a.subList(int fromIndex, int toIndex); //返回ArrayList的基本數組形式 a.toArray(); /**ArrayList克隆,調用方法後,會返回a的一份複製。它與普通的複製(=)不一樣 *賦值後,兩個引用指向同一個對象,會相互影響 *從ArrayList源碼可知,是開闢新的空間來新建一個對象,再把數據複製到新建的對象。 *因此,clone後會造成兩個不一樣的對象,所以不會相互影響。 */ a.clone();
a.remove(Object obj): 若是a是Integer的泛型列表在支持remove對象時要強制轉換爲Ingeger或者Object類型(參考上面例子),若是直接執行a.remove(7),就會調用 a.remove(int index)方法。性能
private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private int size;
DEFAULT_CAPACITY就是上面提到的默認容量大小,從屬性定義可知,容量的默認大小爲10。測試
EMPTY_ELEMENTDATA一個空的數組對象,主要用來與elementData作比較,elementData是否爲空spa
elementData最核心的一個屬性。它是ArrayList的廬山真面目,ArrayList之因此是可變長的,主要是ArrayList內部有elementData這個東東在搞鬼~~。 ArrayList可變長原理:當size大於capacity時,ArrayList就會調用擴容函數新建一個容量更大的elementData數組來代替原來的數組來實現擴容。
總的來講,ArrayList的可變長功能主要時圍繞着capacity與elementData來實現的 設計
實現可變長數組的主要方法:
public void ensureCapacityInternal(int minCapacity)
private void ensureExplicitCapacity(int minCapacity)
private void grow(int minCapacity) //核心方法
ensureCapacityInternal解析
/** * 此函數大概就叫內部容量肯定函數,主要是在每次添加元素這些增大size的 * 動做裏調用,用來判斷需不須要增長capacity大小實現動態分配 */ private void ensureCapacityInternal(int minCapacity) { /** *這個判斷只要是針對使用new ArrayList<E>()構造對象設定,以避免頻繁的擴容。 *若是if判斷爲真,那麼就是使用new ArrayList<E>()構造對象的. *此刻就要選一個最大的容量做爲期待的最小容量,避免頻繁擴容 *詳細解析看下面的例子!! */ if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //調用最終確認方法,需不須要擴容由此方法決定 ensureExplicitCapacity(minCapacity); }
if (elementData == EMPTY_ELEMENTDATA)
做用解析首先,咱們新建一個類(個人爲MyList),把ArrayList的源碼複製過來方便隨心所欲~~,而後把一些報錯除去。最後咱們修改下代碼:
// 把ensureCapacityInternal的if語句註釋掉, /*if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); }*/ 在**grow**方法裏添加一個輸出語句 System.out.println("-----invoke grow()------------");
寫咱們的測試類:
public class Test { public static void main(String[] args) { MyList<Integer> i = new MyList<Integer>(); MyList<Integer> i2 = new MyList<Integer>(10); System.out.println("------使用new MyList<Integer>()------ "); i.add(1); i.add(1); i.add(1); i.add(1); System.out.println("------使用new MyList<Integer>(10)------ "); i2.add(1); i2.add(1); i2.add(1); i2.add(1); }
}
輸出:
———使用new MyList()———
——-invoke grow()——————
——-invoke grow()——————
——-invoke grow()——————
——-invoke grow()——————
———使用new MyList(10)———
能夠看出,若是不是使用if判斷,使用new ArrayList()方法每次添加元素都會調用grow,而每次調用grow都會生成一個新的數組。因此爲了效率內存考慮要添加if判斷。
ensureExplicitCapacity方法
/** *此代碼功能很簡單,只是進行一個簡單的判斷,看是否須要擴容 *若是須要的容量比實際的容量大就進行擴容 */ private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
grow(核心方法)
/** *擴容的具體動做 */ private void grow(int minCapacity) { int oldCapacity = elementData.length; //定義擴容後的容量大小爲原來容量的3/2 int newCapacity = oldCapacity + (oldCapacity >> 1); //若是擴容後的容量仍是比期待的容量小,那麼使用minCapacity爲擴容後的容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); /*這裏實現了動態數組的特性。 *跟蹤copyOf源碼能夠發現,實現方法:新建一個大小爲newCapacity數組, *而後把elementData以前的元素複製到新的數組, *最後把新數組返回f賦值給elementData以實現動態數組 */ elementData = Arrays.copyOf(elementData, newCapacity); }
根據源碼分析,能夠知道:
- ArrayList本質上是一個數組。只不過是數組上包裝了一層華麗的外套而已。
- ArrayList動態數組功能主要思想是,每當數組的空間不夠時,ArrayList會自動把緩衝區(elementData,咱們叫他作緩衝區把~~)的數據複製到一個新的緩衝區裏,(大小通常爲原來的3/2),並令它覆蓋原來的緩衝區成爲本身的緩衝區。
寫到這發現文章太長了,因此決定經常使用方法就寫幾個算了,不全寫了。經常使用方法的源碼都很簡單。大可能是對elementData這個數組操做返回而已。
public boolean add(E e) { //此方法上面已介紹 ensureCapacityInternal(size + 1); //操做elementData,把e添加到數組裏 elementData[size++] = e; return true; }
public E set(int index, E element) { //檢查是否越界,該函數代碼很簡單,就是簡單判斷index與size大小。 rangeCheck(index); //如下代碼跟add對比下就知道他們的區別 E oldValue = elementData(index); elementData[index] = element; return oldValue; }
總的來講,ArrayList的源碼不算難,主要部分實現動態數組那部分,其餘的方法主要仍是在操做elementData數組。感受ArrayList是一個很經常使用的數據結構,看懂源碼對之後使用ArrayList確定有很多幫助,並且解了ArrayList用起來也踏實:-D。