一、索引檢查
1)在指定位置插入元素時,第一步都需要檢查輸入的指定位置是否合法
public void add(int index, E element)==>
{
rangeCheckForAdd(index);
...
}
private void rangeCheckForAdd(int index)
{
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
分析:rangeCheckForAdd方法用於檢查index是否越界。如果該index大於ArrayList元素個數或者小於0時,拋出索引越界異常。
其中outOfBoundsMsg方法,是用來展示IndexOutOfBoundsException detail message。
private String outOfBoundsMsg(int index)
{
return "Index: " + index + ", Size: " + size;
}
2)在指定位置刪除元素時,第一步也需要檢查輸入的索引號是否合法
public E remove(int index)==>
{
rangeCheck(index);
...
}
private void rangeCheck(int index)分析: rangeCheck方法只需檢測index索引是否超出ArrayList元素個數。
{
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
問題1:為什么添加元素和刪除元素使用的索引檢查方法不同呢?
二、確保容量
1)增加容量
public void add(int index, E element)
{
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
...
}
==>
private void ensureCapacityInternal(int minCapacity)
{
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
{
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
其中,minCapacity取與默認值之間的最大值。
ensureExplicitCapacity ==>
private void ensureExplicitCapacity(int minCapacity)
{
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
問題二:size與elementData.length之間的關系?
如果size==elementData.length,if (minCapacity -
elementData.
length > 0)就一直會成立。
分析:
- size很好理解,就是ArrayList的長度大小,也可理解為ArrayList中已添加的元素個數,這里需要注意:ArrayList允許添加為null的元素,所以null也會占用一份空間。
- elementData就是ArrayList的內部數組實現,它是一個一維對象數組。當通過add方法向ArrayList中添加元素時,這些元素就會被依次存儲於elementData這個數組中。 elementData既然是數組,它必然擁有長度length,那么這個數組的長度是否等於ArrayList的size?
這里需要了解一些ArrayList的相關設計概念:
1、作為ArrayList的內部實現數組elementData具備一定的長度(初始值為10),此長度又被稱為capacity(容量)。
2、而每一個ArrayList實例又具有一個size,此size是該實例的列表長度,這其中capacity永遠等於數組elementData的長度,並永遠大於等於列表size。
所以capacity可以理解為ArrayList的容納能力,而size可以理解為已使用的空間大小。用生活中的例子,鞋櫃就像一個容器,容器的大小(capacity)都是不變的,而其中size會隨着填充元素數量增加而增加,但最終只可能等於容器大小。
注意:elementData.length等於elementData.size(),但是ArrayList中的size是記錄ArrayList元素的個數,而elementData是用來存放ArrayList的元素。或者說elementData相當於鞋櫃容器,它的容器大小==elementData.length==elementData.size(),而ArrayList的size相當於鞋子雙數。
參考:List實現之ArrayList
a)容量增長算法
private void grow(int minCapacity)
{
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 得到數組的舊容量,然后進行oldCapacity + (oldCapacity >> 1),將oldCapacity 右移一位,其效果相當於oldCapacity /2,我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍,
- 然后檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那么就把最小需要容量當作數組的新容量,
- 再檢查新容量是否超出了ArrayList所定義的最大容量,若超出了,則調用hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE,如果minCapacity大於最大容量,則新容量則為ArrayList定義的最大容量,否則,新容量大小則為 minCapacity。 (在判斷容量是否超過MAX_ARRAY_SIZE的值,MAX_ARRAY_SIZE值為Integer.MAX_VALUE - 8,比int的最大值小8,不知道為什設計,可能方便判斷吧。如果已經超過,調用hugeCapacity方法檢查容量的int值是不是已經溢出。一般很少用到int最大值的情況,那么多數據也不會用ArrayList來做容器了,估計這輩子沒機會見到hugeCapacity運行一次了。)
- 最后確定了新的容量,就使用Arrays.copyOf方法來生成新的數組,copyOf也已經完成了將就的數據拷貝到新數組的工作。(Arrays.copyof(···)與System.arraycopy(···)區別)
需要注意的是,容量拓展,是創建一個新的數組,然后將舊數組上的數組copy到新數組,這是一個很大的消耗,所以在我們使用ArrayList時,最好能預計數據的大小,在第一次創建時就申請夠內存。
參考:ArrayList實現原理以及其在jdk1.6和jdk1.7的實現區別
hugeCapacity ==>
private static int hugeCapacity(int minCapacity)
{
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2)縮小容量
ArrayList還給我們提供了將底層數組的容量調整為當前列表保存的實際元素的大小的功能。它可以通過trimToSize方法來實現。
public void trimToSize()
{
modCount++;
if (size < elementData.length)
{
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}