Java集合框架02:ArrayList(下)

4、核心方法-add

1、boolean add(E)

/**
* Appends the specified element to the end of this list.
* 添加一個特定的元素到list的末尾。
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//確定內部容量是否夠了,size是數組中數據的個數,因爲要添加一個元素,所以size+1,先判斷size+1的這個個數數組能否放得下,就在這個方法中去判斷是否數組.length是否夠用了。
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e; //在數據中正確的位置上放上元素e,並且size++
    return true;
}

【分析:ensureCapacityInternal(xxx); 確定內部容量的方法】

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity)
    {
    //看,判斷初始化的elementData是不是空的數組,也就是沒有長度
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    //因爲如果是空的話,minCapacity=size+1;其實就是等於1,空的數組沒有長度就存放不了,所以就將minCapacity變成10,也就是默認大小,但是在這裏,還沒有真正的初始化這個elementData的大小。
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //確認實際的容量,上面只是將minCapacity=10,這個方法就是真正的判斷elementData是否夠用
    return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    //minCapacity如果大於了實際elementData的長度,那麼就說明elementData數組的長度不夠用,不夠用那麼就要增加elementData的length。這裏有的同學就會模糊minCapacity到底是什麼呢,這裏給你們分析一下
    /*第一種情況:由於elementData初始化時是空的數組,那麼第一次add的時候,
    minCapacity=size+1;也就minCapacity=1,在上一個方法(確定內部容量
    ensureCapacityInternal)就會判斷出是空的數組,就會給將minCapacity=10,到這一步爲止,還沒有改變elementData的大小。
    第二種情況:elementData不是空的數組了,那麼在add的時候,minCapacity=size+1;也就是minCapacity代表着elementData中增加之後的實際數據個數,拿着它判斷elementData的length是否夠用,如果length不夠用,那麼肯定要擴大容量,不然增加的這個元素就會溢出。*/
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }
    //arrayList核心的方法,能擴展數組大小的真正祕密。
    private void grow(int minCapacity) {
    // overflow-conscious code
    //將擴充前的elementData大小給oldCapacity
    int oldCapacity = elementData.length;
    //newCapacity就是1.5倍的oldCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //這句話就是適應於elementData就空數組的時候,length=0,那麼oldCapacity=0,newCapacity=0,所以這個判斷成立,在這裏就是真正的初始化elementData的大小了,就是爲10.前面的工作都是準備工作。
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    //如果newCapacity超過了最大的容量限制,就調用hugeCapacity,也就是將能給的最大值給
    newCapacity
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    //新的容量大小已經確定好了,就copy數組,改變容量大小咯。
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //這個就是上面用到的方法,很簡單,就是用來賦最大值。
    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;
}    

\1. void add(int,E)

public void add(int index, E element) {
    //檢查index也就是插入的位置是否合理。
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1); // Increments modCount!!
    //這個方法就是用來在插入元素之後,要將index之後的元素都往後移一位,
    System.arraycopy(elementData, index, elementData, index + 1,
    size - index);
    //在目標位置上存放元素
    elementData[index] = element;
    size++;
}

【分析:rangeCheckForAdd(index)】

private void rangeCheckForAdd(int index) {
//插入的位置肯定不能大於size 和小於0
if (index > size || index < 0)
//如果是,就報這個越界異常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

【System.arraycopy(…):就是將elementData在插入位置後的所有元素,往後面移一位.】

public static void arraycopy(Object src,
    int srcPos,
    Object dest,
    int destPos,
    int length)
src:源對象
srcPos:源對象對象的起始位置
dest:目標對象
destPost:目標對象的起始位置
length:從起始位置往後複製的長度。
//這段的大概意思就是解釋這個方法的用法,複製src到dest,複製的位置是從src的srcPost開始,到srcPost+length-1的位置結束,複製到destPost上,從destPost開始到destPost+length-1的位置上,
Copies an array from the specified source array, beginning at the specified
position, to the specified position of the destination array. A subsequence
of array components are copied from
the source array referenced by src to the destination array referenced by
dest. The number of components copied is equal to the length argument. The
components at positions srcPos through srcPos+length-1
in the source array are copied into positions destPos through
destPos+length-1, respectively, of the destination array.
//告訴你複製的一種情況,如果A和B是一樣的,那麼先將A複製到臨時數組C,然後通過C複製到B,用了一個第三方參數
If the src and dest arguments refer to the same array object, then the
copying is performed as if the components at positions srcPos through
srcPos+length-1 were first copied to
a temporary array with length components and then the contents of the
temporary array were copied into positions destPos through destPos+length-1
of the destination array.
//這一大段,就是來說明會出現的一些問題,NullPointerException和
IndexOutOfBoundsException 還有ArrayStoreException 這三個異常出現的原因。
If dest is null, then a NullPointerException is thrown.
If src is null, then a NullPointerException is thrown and the destination
array is not modified.
Otherwise, if any of the following is true, an ArrayStoreException is thrown
and the destination is not modified:
The src argument refers to an object that is not an array.
The dest argument refers to an object that is not an array.
The src argument and dest argument refer to arrays whose component types are
different primitive types.
The src argument refers to an array with a primitive component type and the
dest argument refers to an array with a reference component type.
The src argument refers to an array with a reference component type and the
dest argument refers to an array with a primitive component type.
Otherwise, if any of the following is true, an IndexOutOfBoundsException is
thrown and the destination is not modified:
The srcPos argument is negative.
The destPos argument is negative.
The length argument is negative.
srcPos+length is greater than src.length, the length of the source array.
destPos+length is greater than dest.length, the length of the destination
array.
//這裏描述了一種特殊的情況,就是當A的長度大於B的長度的時候,會複製一部分,而不是完全失敗。
Otherwise, if any actual component of the source array from position srcPos
through srcPos+length-1 cannot be converted to the component type of the
destination array by assignment conversion, an ArrayStoreException is
thrown.
In this case, let k be the smallest nonnegative integer less than length
such that src[srcPos+k] cannot be converted to the component type of the
destination array; when the exception is thrown, source array components
from positions
srcPos through srcPos+k-1 will already have been copied to destination array
positions destPos through destPos+k-1 and no other positions of the
destination array will have been modified. (Because of the restrictions
already itemized,
this paragraph effectively applies only to the situation where both arrays
have component types that are reference types.)
//這個參數列表的解釋,一開始就說了,
Parameters:
src - the source array.
srcPos - starting position in the source array.
dest - the destination array.
destPos - starting position in the destination data.
length - the number of array elements to be copied

【總結】 正常情況下會擴容1.5倍,特殊情況下(新擴展數組大小已經達到了最大值)則只取最大值。 當我們調用add方法時,實際上的函數調用如下:

在這裏插入圖片描述

說明:程序調用add,實際上還會進行一系列調用,可能會調用到grow,grow可能會調用 hugeCapacity。

【舉例】

List<Integer> lists = new ArrayList<Integer>;
lists.add(8);

說明:初始化lists大小爲0,調用的ArrayList()型構造函數,那麼在調用lists.add(8)方法時,會經過怎樣 的步驟呢?下圖給出了該程序執行過程和最初與最後的elementData的大小。

在這裏插入圖片描述

說明:我們可以看到,在add方法之前開始elementData = {};調用add方法時會繼續調用,直至 grow,最後elementData的大小變爲10,之後再返回到add函數,把8放在elementData[0]中。

【舉例說明二】

List<Integer> lists = new ArrayList<Integer>(6);
lists.add(8);

說明:調用的ArrayList(int)型構造函數,那麼elementData被初始化爲大小爲6的Object數組,在調用 add(8)方法時,具體的步驟如下:

說明:我們可以知道,在調用add方法之前,elementData的大小已經爲6,之後再進行傳遞,不會進行 擴容處理。

5、核心方法-remove

其實這幾個刪除方法都是類似的。我們選擇幾個講,其中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;
}

\1. remove(Object):這個方法可以看出來,arrayList是可以存放null值得。

//感覺這個不怎麼要分析吧,都看得懂,就是通過元素來刪除該元素,就依次遍歷,如果有這個元素,
就將該元素的索引傳給fastRemobe(index),使用這個方法來刪除該元素,
//fastRemove(index)方法的內部跟remove(index)的實現幾乎一樣,這裏最主要是知道
arrayList可以存儲null值
public boolean remove(Object o) {
    if (o == null) {
    for (int index = 0; index < size; index++)
    	if (elementData[index] == null) {
    		fastRemove(index);
    		return true;
	    }
    } else {
    for (int index = 0; index < size; index++)
    	if (o.equals(elementData[index])) {
    		fastRemove(index);
    		return true;
  	  }
    }
    return false;
}

\1. clear():將elementData中每個元素都賦值爲null,等待垃圾回收將這個給回收掉,所以叫clear

public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}

\1. removeAll(collection c)

public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);//批量刪除
}

\1. batchRemove(xx,xx):用於兩個方法,一個removeAll():它只清楚指定集合中的元素,retainAll() 用來測試兩個集合是否有交集。

//這個方法,用於兩處地方,如果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;
}

總結:remove函數,用戶移除指定下標的元素,此時會把指定下標到數組末尾的元素向前移動一個單 位,並且會把數組最後一個元素設置爲null,這樣是爲了方便之後將整個數組不被使用時,會被GC,可 以作爲小的技巧使用。

6、其他方法

【set()方法】

說明:設定指定下標索引的元素值

public E set(int index, E element) {
// 檢驗索引是否合法
rangeCheck(index);
// 舊值
E oldValue = elementData(index);
// 賦新值
elementData[index] = element;
// 返回舊值
return oldValue;
}

【indexOf()方法】

說明:從頭開始查找與指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存 放null元素的。與此函數對應的lastIndexOf,表示從尾部開始查找。

// 從首開始查找數組裏面是否存在指定元素
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;
}

【get()方法】

public E get(int index) {
// 檢驗索引是否合法
rangeCheck(index);
return elementData(index);
}

說明:get函數會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),值得注意的 是,在get函數中存在element函數,element函數用於返回具體的元素,具體函數如下:

E elementData(int index) {
return (E) elementData[index];
}

說明:返回的值都經過了向下轉型(Object -> E),這些是對我們應用程序屏蔽的小細節。

4、總結

1)arrayList可以存放null。

2)arrayList本質上就是一個elementData數組。

3)arrayList區別於數組的地方在於能夠自動擴展大小,其中關鍵的方法就是grow()方法。

4)arrayList中removeAll(collection c)和clear()的區別就是removeAll可以刪除批量指定的元素,而 clear是全是刪除集合中的元素。

5)arrayList由於本質是數組,所以它在數據的查詢方面會很快,而在插入刪除這些方面,性能下降很 多,有移動很多數據才能達到應有的效果

6)arrayList實現了RandomAccess,所以在遍歷它的時候推薦使用for循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章