此文章分兩部分,1.ArrayList用法。2.源碼分析。先用法后分析是為了以后忘了查閱起來方便~~
ArrayList 基本用法
1.創建ArrayList對象
//創建默認容量的數組列表(默認為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);
Mark:Capacity 與 Size 時兩個不同的概念:Size: ArrayList包含的元素個數。capacity : ArrayList的容量(默認為10)。它是一個大于或等于size的值。當它的值小于size是就會對ArrayList進行擴容實現可變長數組。簡單來說,capacity就是為了實現可變長數組而設計的。 (具體分析請看下面的源碼分析)
2.ArrayList方法
向ArrayList添加元素/***********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);
獲取ArrayList的元素//獲取特定索引的元素a.get(int index);
Mark:修改(更新)ArrayList的元素遍歷ArrayList: 一般使用for-eachfor(E e : a)dosomething;用for-each的主要優勢:相對傳統的for循環,for-each更簡潔,不容易出錯,而且沒有性能的損失
//用e替換換index位置的元素a.set(int index, E e);
Mark:刪除ArrayList的元素注意: 區分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的其他一些比較常用的方法//獲取ArrayList的元素個數a.size();//判斷ArrayList是否包含某個元素a.contains(Object o)//判斷ArrayList是否是空列表([]),是就返回truea.isEmpty();//獲取ArrayList的子列表,(字串包含索引為fromIndex的元素,不包含toIndex元素)a.subList(int fromIndex, int toIndex);//返回ArrayList的基本數組形式a.toArray();/**ArrayList克隆,調用方法后,會返回a的一份復制。它與普通的復制(=)不同 *賦值后,兩個引用指向同一個對象,會相互影響 *從ArrayList源碼可知,是開辟新的空間來新建一個對象,再把數據復制到新建的對象。*所以,clone后會形成兩個不同的對象,因此不會相互影響。*/a.clone();
Mark: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是否為空
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),并令它覆蓋原來的緩沖區成為自己的緩沖區。
add方法寫到這發現文章太長了,所以決定常用方法就寫幾個算了,不全寫了。常用方法的源碼都很簡單。大多是對elementData這個數組操作返回而已。
public boolean add(E e) { //此方法上面已介紹 ensureCapacityInternal(size + 1); //操作elementData,把e添加到數組里 elementData[size++] = e; return true;}
set方法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。
新聞熱點
疑難解答