Java集合框架-----ArrayList

Java集合框架----ArrayList

之前我們學過數組了,已經知道數組有個缺點,數組一旦被創建,不能自行擴容。現在介紹ArrayList集合容器,它是對數組進行了封裝,使其可以動態擴容和縮小長度,並且ArrayList還可以存儲任意類型的數據。因此ArrayList底層實現的數據結構就是數組。

一、ArrayList集合容器中實現的接口和繼承的類

1)先繼承AbstractList父類,自己本身也實現List接口。繼承和重寫父類和接口中的方法。
2)實現RandomAccess接口:這是有關效率的問題了,實現這個接口說明可以隨機進行存取。
3)實現Serializable接口:實現這個接口,說明ArrayList類可以序列化。
4)實現Cloneable接口:實現和這個接口,可以使用Object.clone()方法了。

二、ArrayList集合容器中的屬性:

1)序列化版本號:
private static final long serialVersionUID = 8683452581122892189L;
2)private static final int DEFAULT_CAPACITY = 10;
默認的初始容量。
3)private static final Object[] EMPTY_ELEMENTDATA = {};
如果自定義長度爲0,則使用它來初始化ArrayList,或者用於空數組替換。
4)private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
如果沒有自定義容量,則使用它來初始化ArrayList,或者用於空數組的對比。
transient Object[] elementDate;
這是ArrayList底層用到的數組,前面關鍵字transient關鍵字說明這個變量已經實現序列化的類中,不允許再序列化。
5)private int size;
這個變量是用來記錄實際的ArrayList大小。
6)private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
可分配的最大容量。(但是在Java8的源碼中已經沒有這個屬性了)。

三、ArrayList的構造方法:

1)無參構造函數:

public ArrayList(){
/**
*   無參構造函數,設置元素數組爲空
*/
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2)int數據類型的參數:
參數爲數組的初始化長度,首先需要判斷initialCapacity和0的關係。
  如果initialCapacity的值大於0,則創建一個長度爲initialCapacity的對象數組。
  如果initialCapacity的值等於0,則將EMPTY_ELEMENTDATA賦給elementData。
  如果initialCapacity的值小於0,則拋出異常,非法容量。

public ArrayList(int initialCapacity){
if(initialCapacity > 0){
//當初始化容量大於0,則新建一個Object的數組;
this.elementData = new Object[initialCapacity];
}else if(initialCapacity == 0){
//當初始容量爲0的時候,則初始化爲空對象數組
this.elementData = EMPTY_ELEMENTDATA;
}else{
//如果初始化容量小於0,這個時候就需要拋出異常了
throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
}
}

3) Collection<? extends E>類型的參數:
  第一步:將參數中的集合轉換成數組,賦值給elementData。
  第二步:判斷參數集合是否爲空,通過比較size和第一步中的集合比較。
  第三步:如果參數集合爲空,則設置元素數組爲空,即將EMPTY_ELEMENTDATA的值賦給elementData。
  第四步:如果參數集合不爲空,接下來判斷是否能夠成功轉換成Object數組,如果轉換成Object類型的數組,則將數組進行復制。

public ArrayList(Collection &lt;? extends E&gt; c){
//轉換成數組
elementData = c.toArray();
//判斷參數容器的長度:
if((size = elementData.length) != 0){
//判斷是否能夠成功轉換成Object數組,如果不可以就複製數組
if(elementData.getClass() != Object[].class){
elementData = Arrays.copyOf(elementData,size,Object[].class);
}
}else{
//說明參數容器中的實際長度爲0,則將空對象數組賦值給elementData
elementData = EMPTY_ELEMENTDATA;
}
}

四、ArrayList的常用方法:

1)add(E e):
在添加的方法中,主要需要完成以下三個步驟:
步驟1:判斷容器中容量,如果容量不足,則需要動態擴容。
步驟2:將元素添加到容器中的指定位置。
步驟3:將元素中的實際元素個數加1,也就是size++;

/**
* ArrayList的增加方法的源碼:
*/
public boolean add(E e){
ensureCapacityInternal(size+1);
elementData[size++] = e;
return true;
}

在上面的代碼中,如果ArrayList容器的容量不足的時候,則需要進行動態擴容,下面介紹ArrayList的擴容機制:
ArrayList的擴容機制是在ArrayList容器的add(E e)方法的時候會啓用,擴容機制也是爲了確保ArrayList容器可以將要添加的元素成功添加上去。一次擴容需要經歷以下步驟:
1)在方法中首先調用了ensureCapacityInternal(size+1)的方法,用來確定添加元素成功需要的最小集合容量minCapacity,size+1是爲了當元素添加成功之後,則將容器中的實際容量加1。

2)調用ensureExplicitCapacity(minCapacity)方法來確保集合容器爲了將元素添加成功,是否需要擴容,將計數器加1,然後判斷minCapacity和當前數組的長度大小,當minCapacity的值比當前數組的長度大時,則說明需要擴容。

3)當容器需要擴容的時候,則調用grow(minCapacity)方法,minCapacity表示確保元素添加成功的最小容量,在擴容的時候,首先對原數組的長度增加1.5倍,然後對擴容後的容量和minCapacity比較,如果比新容量比minCapacity的小,則將minCapacity的值設置爲容量,否則設置爲新容量。再將原數組拷貝到新容器中。

/**
* ensureCapacityInternal(size + 1)方法,用來確定是否需要擴容。
*/
private void ensureCapacityInternal(int minCapacity){
//括號中的參數calculateCapacity是用來計算容量的
ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}
/**
*   計算容量的方法calculateCapacity(elementData,minCapacity)方法
*/
private static int calculateCapacity(Object[] elementData,int minCapacity){
//如果elementData的數組爲空數組:
if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
//返回默認空數組的長度和傳入參數兩個中較大的值
return Math.max(DEFAULT_CAPACITY,minCapacity);
}
//否則直接返回minCapacity;
return minCapacity;
}
/**
*   確保容量可用的方法:ensureExplicitCapacity(minCapacity)
*/
private void ensureExplicitCapacity(int minCapacity){
//結構性修改+1;
modCount++;
//如果minCapacity的值大於了當前數組的長度,則說明數組存放滿了,需要擴容。
if(minCapacity - elementData.length &gt; 0){
grow(minCapacity);
}
} 
/**
* 數組擴容:grow(int minCapacity);
*/
private void grow(int minCapacity){
//獲取原有數組的容量大小:
int oldCapacity = elementData.length;
//計算新容量,新容量爲舊容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity &gt;&gt; 1);
//判斷如果新容量小於參數指定容量,則新容量修改爲參數容量minCapacity;
if(newCapacity - minCapacity &lt; 0){
newCapacity = minCapacity;
}
//判斷如果新容量大於了最大容量(Integer.MAX_VALUE-8),則執行新的內容。
if(newCapacity - MAX_ARRAY_SIZE &gt; 0){
newCapacity = hugeCapacity(minCapacity);
}
//拷貝擴容:
elementData = Arrays.copyOf(elementData,newCapacity);
}

2)set(int index,E element):設置指定索引處的元素值。
在對象調用set方法的時候,會做以下三個步驟:
步驟1:校驗輸入參數下標是否合法。
步驟2:將數組中該下標值原有的元素值取出。
步驟3:將參數新的元素設置到該位置,然後將原有的元素值作爲返回值返回。

/**
*   set(int index,E element)方法源碼
*/
pubic E set(int index,E element){
//校驗輸入的參數是否合法:
rangeCheck(index);
//獲取到該下標位置的原有元素值
E oldValue = elementData(index);
//將該小標位置上設置上新元素
elementData[index] = element;
//將該下標位置的原有元素值返回
return oldValue;
}
/**
*   校驗輸入參數是否合法的方法
*/
private void rangeCheck(int index){
if(index &gt;= size){
//拋出異常:
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}

3)indexOf(Object obj):找出該容器中第一次出現obj的索引
indexOf方法是從容器的開頭開始查找與傳入元素是否相等,如果相等,則返回第一次出現該元素的索引值,否則返回-1.
注意:通過源碼可以知道,在查找是否有相同元素的,因爲ArrayList集合中可以存放null值,所以要區分傳入的參數是否爲null,否則在比較的時候會出現空指向的異常

/**
*   indexOf查找和傳入參數相同的下標值
*/
public int indexOf(Object obj){
if (obj == null) {
//遍歷容器,找到爲null元素的下標
for (int i = 0;i &lt; size;i++) {
//如果找到元素爲null的值,則返回下標值
if (elementData[i] == null) {
return i;
}
}
} else {
//遍歷容器
for (int i = 0;i &lt; size;i++) {
//如果找到了和傳入參數的值相等的下標,則返回下標
if (obj.equals(elementData[i])) {
return i;
}
}
}
//沒有找到則返回-1
return -1;
}

3)get(int index):找出指定下標的元素值
步驟1:檢查輸入的參數是否合法
步驟2:如果參數合法,則將集合容器中的元素值返回

/**
*  get(int index):找出指定下標元素值的方法源碼
*/
public E get(int index){
//校驗下標值是否合法
rangeCheck(index);
//如果下標參數合法,則返回該下標的元素值
return elementData(index);
}

4)remove(int index):根據下標值刪除該位置的元素:
步驟1:將下標位置處往後的所有元素往前面(左邊)移動一位 。
步驟2:取出index下標位置的原有元素值。
步驟3:計算需要移動的元素個數。
步驟4:將最後一個位置的元素賦值爲null,有助於gc
步驟5:將該index位置的原有元素返回。

/**
*  remove(int index):刪除指定下標的元素值,並返回原有元素值的源碼
*/
public E remove(int index){
//檢測傳入參數下標值是否合法:
rangeCheck(index);
//記錄實際操作的測試
modCount++;
//獲取到原有元素值:
E oldValue = elementData(index);
//計算需要移動的元素個數:
int numMoved = size - index - 1;
//判斷移動元素的個數是否大於0
if(numMoved &gt; 0){
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
//將最後一個下標元素賦值爲null
elementData[--size] = null;
//返回原有元素:
return oldValue;
}

總結:ArrayList的優缺點
優點:
1、ArrayList底層爲數組,並且ArrayList是實現了RandomAccess接口,可以實現隨機訪問模式,因此查詢和修改效率高。
2、可以自動擴容,每次擴容爲原來容量的1.5倍
缺點:
1、ArrayList的刪除和插入效率較慢.
2、根據查看源碼得知,其中的方法indexOf(int index),因爲需要遍歷整個元素,因此效率不高。
3、線程是不安全的。

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