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 <? extends E> 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 > 0){ grow(minCapacity); } }
/** * 數組擴容:grow(int minCapacity); */ private void grow(int minCapacity){ //獲取原有數組的容量大小: int oldCapacity = elementData.length; //計算新容量,新容量爲舊容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //判斷如果新容量小於參數指定容量,則新容量修改爲參數容量minCapacity; if(newCapacity - minCapacity < 0){ newCapacity = minCapacity; } //判斷如果新容量大於了最大容量(Integer.MAX_VALUE-8),則執行新的內容。 if(newCapacity - MAX_ARRAY_SIZE > 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 >= 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 < size;i++) { //如果找到元素爲null的值,則返回下標值 if (elementData[i] == null) { return i; } } } else { //遍歷容器 for (int i = 0;i < 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 > 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、線程是不安全的。