一、前言
要想深入的了解集合就必須要通過分析源碼來了解它,那如何來看源碼,要看什么東西呢?主要從三個方面:
1、看繼承結構
看這個類的繼承結構,處於一個什么位置,不需要背記,有個大概的感覺就可以,我自己感覺了解了之后心里都舒服些。
2、看構造方法
很重要,一般在構造方法中會做很多事情,要跟蹤方法中的方法。
3、看常用方法
沒必要所有方法都去了解,知道常用、核心的方法實現即可。
本文參考:https://www.cnblogs.com/zhangyinhua/p/7687377.html#_label3_1_3_2
二、ArrayList概述
1)ArrayList是可以動態增長和縮減的索引序列,它是基於數組實現的List類。
2)該類封裝了一個動態再分配的Object[]數組,每一個類對象都有一個capacity屬性,表示它們所封裝的Object[]數組的長度,當向ArrayList中添加元素時,該屬性值會自動增加。
3)如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以減少增加重分配的次數提高性能。
4)ArrayList的用法和Vector向類似,但是Vector是一個較老的集合,具有很多缺點,不建議使用。
另外,ArrayList和Vector的區別是:ArrayList是線程不安全的,當多條線程訪問同一個ArrayList集合時,程序需要手動保證該集合的同步性,而Vector則是線程安全的。
5)繼承關系圖:
三、源碼分析
3.1、繼承結構和層次關系
分析:
為什么要讓AbstractList先實現List<E>,然后在讓ArrayList繼承AbstractList?為什么不讓ArrayList直接實現List<E>?
這里是一種默認的寫法,也可以說是一種思想:讓AbstractList去實現接口中一些通用的方法,而具體的類ArrayList就繼承這個AbstractList類,拿到一些通用的方法,然后自己在實現一些自己特有的方法。
這樣一來代碼更簡潔,並且如果有多個類繼承ArrayList,就可以直接繼承ArrayList中通用的方法,減少重復代碼。所以一般看到一個類上面還有一個抽象類,應該就是這個作用。
3.2、類中屬性
沒什么可說的,看注釋即可。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 版本號
private static final long serialVersionUID = 8683452581122892189L; // 缺省容量 private static final int DEFAULT_CAPACITY = 10; // 空對象數組 private static final Object[] EMPTY_ELEMENTDATA = {}; // 缺省空對象數組 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 元素數組 transient Object[] elementData; // 實際元素大小,默認為0 private int size; // 最大數組容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; }
3.3、構造方法
ArrayList有三個構造方法:
1、無參構造函數
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一個空的Object[],將elementData初始化,elementData也是個Object[]類型。空的Object[]會給默認大小10,等會解釋什么時候賦值的。
ArrayList中儲存數據的其實就是一個數組,這個數組就是elementData,下圖中有
2、有參構造函數一
邏輯很簡單,不解釋了。
3、有參構成函數二(不常用)
總結:arrayList的構造方法就做一件事情,就是初始化一下儲存數據的容器,其實本質上就是一個數組,在其中就叫elementData。
3.4、核心方法
3.4.1、添加方法
添加方法共有四個,這里只介紹常用的兩種。
1)boolean add(E);//默認直接在末尾添加元素
分析:
ensureCapacityInternal方法為確定容量方法。
在添加元素之前需要確定數組是否能放的下,size是數組中數據的個數,因為要添加一個元素,所以size+1。
ensureCapacityInternal方法中分兩步:
a、首先確定最小容量:判斷elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即判斷初始化的elementData是不是空的數組。然后找出默認容量和參數容量中大的。
b、調用ensureExplicitCapacity方法,該方法才是真的判斷容量是否夠用的方法,如果不過用則擴容
在ensureExplicitCapacity方法中,如果需要的容量大於elementData的容量,則調用grow方法進行擴容,grow方法是真正的擴容方法。
至於modCount++這個作用很多,比如用來檢測快速失敗的一種標志,這個對於我們目前研究的問題無影響,不用在意。
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //將擴充前的elementData大小給oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity if (newCapacity - minCapacity < 0)//這句話就是適應於elementData就空數組的時候,length=0,那么oldCapacity=0,newCapacity=0,所以這個判斷成立,在這里就是真正的初始化elementData的大小了,就是為10.前面的工作都是准備工作。 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超過了最大的容量限制,就調用hugeCapacity,也就是將能給的最大值給newCapacity newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //新的容量大小已經確定好了,就copy數組,改變容量大小咯。 elementData = Arrays.copyOf(elementData, newCapacity); }
hugeCapacity();
//這個就是上面用到的方法,很簡單,就是用來賦最大值。 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //如果minCapacity都大於MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之將MAX_ARRAY_SIZE返回。因為maxCapacity是三倍的minCapacity,可能擴充的太大了,就用minCapacity來判斷了。 //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是說最大也就能給到第一個數值。還是超過了這個限制,就要溢出了。相當於arraylist給了兩層防護。 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
2)void add(int,E);在特定位置添加元素,也就是插入元素
public void add(int index, E element) { rangeCheckForAdd(index);//檢查index也就是插入的位置是否合理。 //跟上面的分析一樣,具體看上面 ensureCapacityInternal(size + 1); // Increments modCount!! //這個方法就是用來在插入元素之后,要將index之后的元素都往后移一位, System.arraycopy(elementData, index, elementData, index + 1, size - index); //在目標位置上存放元素 elementData[index] = element; size++;//size增加1 }
分析:
rangeCheckForAdd方法
注意:
當調用空的構成函數創建ArrayList時,初始化List大小是在第一次添加時進行。
3.4.2、刪除方法
和添加方法一樣,這幾個刪除方法都是類似的,抽兩個分析下就行,其他的都差不多。
另外,fastRemove(int)方法是private的,是提供給remove(Object)這個方法用的。
1)remove(int):通過刪除指定位置上的元素
public E remove(int index) { rangeCheck(index);//檢查index的合理性 modCount++;//這個作用很多,比如用來檢測快速失敗的一種標志。 E oldValue = elementData(index);//通過索引直接找到該元素 int numMoved = size - index - 1;//計算要移動的位數。 if (numMoved > 0) //這個方法也已經解釋過了,就是用來移動元素的。 System.arraycopy(elementData, index+1, elementData, index, numMoved); //將--size上的位置賦值為null,讓gc(垃圾回收機制)更快的回收它。 elementData[--size] = null; // clear to let GC do its work //返回刪除的元素。 return oldValue; }
2)remove(Object):這個方法可以看出來,arrayList是可以存放null值得。
3)clear():將elementData中每個元素都賦值為null,等待垃圾回收將這個給回收掉,所以叫clear
4)removeAll(collection c)批量刪除
分析:
batchRemove(xx,xx):
//這個方法,用於兩處地方,如果complement為false,則用於removeAll如果為true,則給retainAll()用,retainAll()是用來檢測兩個集合是否有交集的。 private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; //將原集合,記名為A int r = 0, w = 0; //r用來控制循環,w是記錄有多少個交集 boolean modified = false; try { for (; r < size; r++) //參數中的集合C一次檢測集合A中的元素是否有, if (c.contains(elementData[r]) == complement) //有的話,就給集合A elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. //如果contains方法使用過程報異常 if (r != size) { //將剩下的元素都賦值給集合A, System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { //這里有兩個用途,在removeAll()時,w一直為0,就直接跟clear一樣,全是為null。 //retainAll():沒有一個交集返回true,有交集但不全交也返回true,而兩個集合相等的時候,返回false,所以不能根據返回值來確認兩個集合是否有交集,而是通過原集合的大小是否發生改變來判斷,如果原集合中還有元素,則代表有交集,而元集合沒有元素了,說明兩個集合沒有交集。 // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
3.4.3、indexOf方法
// 從首開始查找數組里面是否存在指定元素 public int indexOf(Object o) { if (o == null) { // 查找的元素為空 for (int i = 0; i < size; i++) // 遍歷數組,找到第一個為空的元素,返回下標 if (elementData[i]==null) return i; } else { // 查找的元素不為空 for (int i = 0; i < size; i++) // 遍歷數組,找到第一個和指定元素相等的元素,返回下標 if (o.equals(elementData[i])) return i; } // 沒有找到,返回空 return -1; }
3.4.4、get方法
要先檢測所以是否合法。
注意:在get函數中存在element函數,element函數用於返回具體的元素,具體函數如下:
說明:返回的值都經過了向下轉型(Object -> E),這些是對我們應用程序屏蔽的小細節。
四、總結
1)arrayList可以存放null。
2)arrayList本質上就是一個elementData數組。
3)arrayList區別於數組的地方在於能夠自動擴展大小,其中關鍵的方法就是gorw()方法。
4)arrayList中removeAll(collection c)和clear()的區別就是removeAll可以刪除批量指定的元素,而clear是刪除集合中的全部元素。
5)arrayList由於本質是數組,所以它在數據的查詢方面會很快,而在插入刪除這些方面,性能下降很多,因為需要移動很多數據才能達到應有的效果
6)arrayList實現了RandomAccess,所以在遍歷它的時候推薦使用for循環。