JDK集合原始碼分析(一)ArrayList
阿新 • • 發佈:2018-12-16
1.ArrayList簡介
1)對陣列的封裝。可以動態增長和縮減。是一種基於陣列實現的List類。是一種順序表。 2)ArrayList和Vector比較。Vector是一個較老的集合,具有很多缺點,不建議使用。ArrayList是執行緒不安全的,當多條執行緒訪問同一個ArrayList集合時,程式需要手動保證該集合的同步性,而Vector則是執行緒安全的。 3)ArrayList的類圖 (圖片來自百度相簿)
2.類繼承結構:
3.類中的屬性
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 = {}; //預設的空物件陣列 //預設使用的初始化陣列 和上面區別在於當第一個元素被加入進來的時候它知道如何擴張在add(E e)中第一行體現 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //元素陣列 transient Object[] elementData; // non-private to simplify nested class access //實際元素大小,預設為0 private int size;
4.構造方法
ArrayList有三個構造方法:
4.1無參構造方法
/**
* ArrayList三個構造方法
* 作用就是初始化核心陣列 elementData
*/
//建構函式三者之一:無參構造()
//預設容量10,實際Object陣列賦值一個預設的空物件陣列
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
4.2有參構造方法——指定大小
//建構函式 三者之二:指定初始大小(int) public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { //如果自定義大小小於0,報非法資料異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
4.3有參構造方法——指定大小
//建構函式三者之三:構造(Collection) 不常用 //用法:將Collection<Student> 轉換為 source.ArrayList<Person> public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); //先轉換為陣列 if ((size = elementData.length) != 0) { //陣列元素不為0 if (elementData.getClass() != Object[].class) //每個集合的toarray()的實現方法不一樣 所以判斷若返回不是Object[]型別,則用Arrays轉換成Object型別 elementData = Arrays.copyOf(elementData, size, Object[].class); //拷貝成Object型別的陣列 } else { //陣列個數為0,則賦值空物件陣列 this.elementData = EMPTY_ELEMENTDATA; } }
粘上copyOf的實現:
5.主要方法
5.1 add()方法(有4個)
1)boolean add(E);//預設直接在末尾新增元素
// add方法四之一:(E)
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判斷新增一個後容量變成size+1的這個個數 陣列能否放得下 和陣列.length比較 // Increments modCount!!
elementData[size++] = e; //新增在陣列末尾
return true;
}
2)void add(int,E);在特定位置插入元素
// add方法四之二:在特定位置插入元素
public void add(int index, E element) {
rangeCheckForAdd(index); //不在0~size之內的,就丟擲IndexOutOfBoundsException
ensureCapacityInternal(size + 1); //確保容得下插入的元素// Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //從index開始所有的元素都後移一格
elementData[index] = element;//然後把要插入的元素放到index位置
size++;
}
//確保minCapacity不會超出現在的陣列大小
private void ensureCapacityInternal(int minCapacity) { //minCapacity表示當前實際size+1,放第一個元素時size(0)+1=1
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //假如元素陣列還是空陣列,空陣列沒法放元素,需要確定初始增加多大容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY=10;minCapacity是10一下全為10,10以上為minCapacity
}
//上面只是提升擴容的閾值(10)
//確認實際的容量,上面只是第一次放元素時將minCapacity=10,而後來放元素相當於只是對ensureExplicitCapacity的封裝
ensureExplicitCapacity(minCapacity);
}
//確保minCapacity不會超出現在的陣列大小(超過就擴容)
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //修改次數自增
/*第一種情況:由於elementData初始化時是空的陣列,那麼第一次add的時候,minCapacity=size+1;也就minCapacity=1,在上一個方法(確定內部容量ensureCapacityInternal)就會判斷出是空的陣列,就會給
將minCapacity=10。
第二種情況:elementData不是空的陣列,那麼在add的時候,minCapacity=size+1;也就是minCapacity代表著elementData中增加之後的實際資料個數,拿著它判斷elementData的length是否夠用,如果length
不夠用,那麼肯定要擴大容量。
*/
if (minCapacity - elementData.length > 0) //minCapacity如果大於了實際elementData陣列的長度,那麼就說明elementData陣列的長度不夠用,
//arrayList能自動擴充套件大小的關鍵方法就在這裡了
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length; //擴充前的elementData大小
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
if (newCapacity - minCapacity < 0)//如果擴充1.5倍還是不夠 那就用minCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //如果newCapacity超過了最大的容量限制(Integer.MAX_VALUE - 8)(這個數字可能其他地方會用到,所以有這麼一出),就呼叫hugeCapacity,也就是將能給的最大值(Integer.MAX_VALUE)給newCapacity
newCapacity = hugeCapacity(minCapacity);
//新的容量大小已經確定好了,就copy陣列,改變容量大小。
elementData = Arrays.copyOf(elementData, newCapacity);
}
3)boolean addAll(Collection<? extends E> c) 在末尾插入所有collection中的元素
//add方法四之三: 往末尾插一個Collection
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew); //從a陣列拷貝到elementData陣列
size += numNew;
return numNew != 0;
}
4)boolean addAll(int index, Collection<? extends E> c) 往指定位置插一個Collection
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); //從index開始的元素統統向後移動index+numNew
System.arraycopy(a, 0, elementData, index, numNew);//再將a陣列放到index位置
size += numNew;
return numNew != 0;
}
5.2 刪除方法
這幾個刪除方法都是類似的。我們選擇幾個看
1)remove(int):通過刪除指定位置上的元素
//刪除方法5 之1:刪除指定位置上的元素
public E remove(int index) {
rangeCheck(index); //元素大於等於size 拋異常
modCount++; //修改次數 加1
E oldValue = elementData(index); //獲取老值,以便回收
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved); //index後面的所有元素都前移一格
elementData[--size] = null; //所有元素前移後,最後一個元素沒用了,置空,儘快回收 // clear to let GC do its work
return oldValue;
}
//不用檢測負的,因為如果索引為負,會在訪問陣列的時候,丟擲ArrayIndexOutOfBoundsException
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
2)remove(Object):根據Object元素查詢刪除此object
//刪除方法5 之2:根據Object元素查詢刪除此object
public boolean remove(Object o) {
if (o == null) { //要刪除 null 最主要是知道arrayList可以儲存null值!!!
for (int index = 0; index < size; index++)
if (elementData[index] == null) {//刪掉遇到的第一個null元素
fastRemove(index);
return true; //刪除成功返回true
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) { //用equals判斷
fastRemove(index); //刪除遇到的第一個o元素
return true; //刪除成功返回true
}
}
return false; //刪除失敗返回false
}
//私有方法,僅供remove(Object)使用,相比public的remove,"快"在了: 1.沒有邊界檢查(因為在remove(Object)中已經確保了邊界) 2.沒有返回刪除掉的元素
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved); //所有的元素向前移動一格
elementData[--size] = null; //將最後一個置為null 儘快回收// clear to let GC do its work
}
3)removeAll(collection c)://批量刪除
//刪除在Collection c中的所有元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c); //確保c非空 否則拋NullPointerException 空指標異常
return batchRemove(c, false); //批量刪除
}
// 刪除存在於Collection c 中的所有的所有元素
//用於兩個方法,(complement為false時) removeAll():清除指定集合中的元素;(complement為true時)retainAll()用來測試兩個集合是否有交集。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0; //r用來控制迴圈,w是記錄有多少個交集
boolean modified = false; //標記陣列結構是否修改過
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement) //依次檢查集合c中是否包含本元素陣列中的所有元素
elementData[w++] = elementData[r]; //從0開始,收集滿足條件的元素(不包含或者包含),這塊複用了elementData。如果complement是false,就是將不包含在c中的元素挑揀出來
} finally {
//如果contails方法使用過程報出異常
if (r != size) { //沒有遍歷完,篩選中途報了異常 說明遍歷中途丟擲了異常
//將剩下的元素都賦值給elementData
System.arraycopy(elementData, r,//將還沒有經過遍歷篩選的元素 全部拷貝到篩選出來的元素末尾
elementData, w,
size - r); //一共拷貝r到size-1位置的元素
w += size - r; //實際挑揀出來的元素數目 加上r後面沒有遍歷到的所有元素
}
if (w != size) { //挑選出來的元素只是一部分,不是全部,將沒選中的元素清除出去
// clear to let GC do its work
for (int i = w; i < size; i++) //將挑選出來的元素後面的元素全部置空 加快回收
elementData[i] = null;
modCount += size - w; //修改次數加上置空的元素個數
size = w; //this.elementData實際容量變為w(即只要挑選出來的元素)
modified = true;
}
}
return modified;
}
附帶看下 clear()實現,它與remove不同
//將陣列中所有元素賦值為null 以便GC快速回收掉
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
5.3 indexOf()方法
//查詢某個元素的下標,對應lastIndexOf()是從尾部開始找
public int indexOf(Object o) {
if (o == null) { //要查詢的元素為 null
for (int i = 0; i < size; i++) //查詢第一個為null的元素位置
if (elementData[i]==null)
return i;
} else { //要查詢的元素不為 null
for (int i = 0; i < size; i++) //找到第一個此元素的下標返回
if (o.equals(elementData[i])) //用equals比較,所以得分null和非null
return i;
}
return -1;
}
5.4 get()方法
public E get(int index) {
rangeCheck(index); //檢驗索引合法 index >= size 拋越界異常
return elementData(index);
}
5.5 set()方法
//在index位置覆蓋element元素
public E set(int index, E element) {
rangeCheck(index); //檢測索引合法 index >= size 拋異常
E oldValue = elementData(index);
elementData[index] = element;
return oldValue; //返回舊值
}
6. 總結
1)(是陣列)arrayList本質上就是一個elementData陣列。 2)(能擴充套件)arrayList區別於陣列的地方在於能夠自動擴充套件大小,其中關鍵的方法就是gorw()方法。 3)(可放null)arrayList可以存放null。 4)(可置空)arrayList中removeAll(collection c)和clear()的區別就是removeAll可以刪除批量指定的元素,而clear是將集合中的元素全部置為null,以便快速回收。 5)arrayList由於本質是陣列,所以它在資料的查詢方面會很快,而且arrayList標記了RandomAccess,所以在遍歷它的時候推薦使用for迴圈;而在插入刪除這些方面,效能下降很多,有移動很多資料才能達到應有的效果。