Java SE——ArrayList
一ArrayList定義
1 實現RandomAccess介面:
這也是一個標記介面,介面中沒有任何方法和常量,表名該類支援快速的隨機訪問。在工具類Collections
中應用二分查詢方法時判斷了是否實現了該介面
1 int binarySearch(List<? extends Comparable<? super T>> list, T key) { 2 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) 3 return Collections.indexedBinarySearch(list, key); 4 else 5 return Collections.iteratorBinarySearch(list, key); 6 }
2 實現Cloneable介面:
淺拷貝可以通過呼叫Object.clone();但是呼叫該方法的物件一定要實現該介面,否則會丟擲CloneNoSupportException。
3 實現了Serializable介面:
表示可以序列化
4 實現list介面:
list介面是list集合類的上層介面,實現該介面的類都必須實現一些方法
二 欄位屬性:
//集合的預設大小 private static final int DEFAULT_CAPACITY = 10; //空的陣列例項 private static final Object[] EMPTY_ELEMENTDATA = {}; //這也是一個空的陣列例項,和EMPTY_ELEMENTDATA空陣列相比是用於瞭解新增元素時陣列膨脹多少 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //儲存 ArrayList集合的元素,集合的長度即這個陣列的長度 //1、當 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時將會清空 ArrayList //2、當新增第一個元素時,elementData 長度會擴充套件為 DEFAULT_CAPACITY=10 transient Object[] elementData; //表示集合的長度 private int size;
三 建構函式:
此無參建構函式將建立一個DEFAULTCAPACITY_EMPTY_ELEMENTDATA 宣告的陣列,注意此時初始容量是0,而不是大家以為的 10。
注意:根據預設建構函式建立的集合,ArrayList list = new ArrayList();此時集合長度是0.
建立一個指定大小的陣列,等於0時,就建立一個空陣列,小於0時,丟擲IllegalArgumentException異常。
將已有的集合複製到ArrayList中。
四 方法:
通過前面的欄位屬性和建構函式,我們知道 ArrayList 集合是由陣列構成的,那麼向 ArrayList 中新增元素,也就是向陣列賦值。我們知道一個數組的宣告是能確定大小的,而使用 ArrayList 時,好像是能新增任意多個元素,這就涉及到陣列的擴容。
擴容的核心方法就是呼叫Arrays.copyOf 方法,建立一個更大的陣列,然後將原陣列元素拷貝過去即可。下面我們看看具體實現。
如上圖所示,增加元素之前會呼叫ensureCapacityInternal()方法來確定集合的大小,如果集合滿了,就要擴容操作。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//這裡的minCapacity 是集合當前的大小+1,可以看add的傳參
private void ensureCapacityInternal(int minCapacity) {
//elementData 是實際用來儲存元素的陣列,注意陣列的大小和集合的大小不是相等的,前面的size是指集合大小
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
接下來就是擴容的方法:
1 private void grow(int minCapacity) {
2 int oldCapacity = elementData.length;//得到原始陣列的長度
3 int newCapacity = oldCapacity + (oldCapacity >> 1);//新陣列的長度等於原陣列長度的1.5倍
4 if (newCapacity - minCapacity < 0)//當新陣列長度仍然比minCapacity小,則為保證最小長度,新陣列等於minCapacity
5 newCapacity = minCapacity;
6 //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 = 2147483639
7 if (newCapacity - MAX_ARRAY_SIZE > 0)//當得到的新陣列長度比 MAX_ARRAY_SIZE 大時,呼叫 hugeCapacity 處理大陣列
8 newCapacity = hugeCapacity(minCapacity);
9 //呼叫 Arrays.copyOf 將原陣列拷貝到一個大小為newCapacity的新陣列(注意是拷貝引用)
10 elementData = Arrays.copyOf(elementData, newCapacity);
11 }
12
13 private static int hugeCapacity(int minCapacity) {
14 if (minCapacity < 0) //
15 throw new OutOfMemoryError();
16 return (minCapacity > MAX_ARRAY_SIZE) ? //minCapacity > MAX_ARRAY_SIZE,則新陣列大小為Integer.MAX_VALUE
17 Integer.MAX_VALUE :
18 MAX_ARRAY_SIZE;
19 }
對於 ArrayList 集合新增元素,我們總結一下:
①、當通過 ArrayList() 構造一個空集合,初始長度是為0的,第 1 次新增元素,會建立一個長度為10的陣列,並將該元素賦值到陣列的第一個位置。
②、第 2 次新增元素,集合不為空,而且由於集合的長度size+1是小於陣列的長度10,所以直接新增元素到陣列的第二個位置,不用擴容。
③、第 11 次新增元素,此時 size+1 = 11,而陣列長度是10,這時候建立一個長度為10+10*0.5 = 15 的陣列(擴容1.5倍),然後將原陣列元素引用拷貝到新陣列。並將第 11 次新增的元素賦值到新陣列下標為10的位置。
④、第 Integer.MAX_VALUE - 8 = 2147483639,然後 2147483639%1.5=1431655759(這個數是要進行擴容) 次新增元素,為了防止溢位,此時會直接建立一個 1431655759+1 大小的陣列,這樣一直,每次新增一個元素,都只擴大一個範圍。
⑤、第 Integer.MAX_VALUE - 7 次新增元素時,建立一個大小為 Integer.MAX_VALUE 的陣列,在進行元素新增。
⑥、第 Integer.MAX_VALUE + 1 次新增元素時,丟擲 OutOfMemoryError 異常。
注意:能向集合中新增 null 的,因為陣列可以有 null 值存在。
刪除元素:
- 根據給定索引刪除指定元素:
/**
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);//判斷給定索引的範圍,超過集合大小則丟擲異常
modCount++;
E oldValue = elementData(index);//得到索引處的刪除元素
int numMoved = size - index - 1;
if (numMoved > 0) //size-index-1 > 0 表示 0<= index < (size-1),即索引不是最後一個元素
//通過 System.arraycopy()將陣列elementData 的下標index+1之後長度為 numMoved的元素拷貝到從index開始的位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work 將陣列最後一個元素置為 null,便於垃圾回收
return oldValue; // 將被刪除的元素返回
}
2.根據元素值刪除:
/**
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) { //用if判斷了一次 就直接返回,所以remove是刪除第一次出現的元素
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
修改元素:
修改元素就比較簡單,先呼叫rangeCheck判斷給定索引是否超出範圍,再替換索引出元素,返回被替換的值。
/**
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
獲得元素:
根據索引獲得元素。
根據元素獲得索引,還有 lastIndexOf(Object o) 方法是返回最後一次出現該元素的下標。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null) //依然是用的if判斷就直接返回,所以也是獲得第一次出現該元素的索引
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1; //如果沒有,就返回-1;
}