ArrayList用法詳解與源碼分析

說明

此文章分兩部分,1.ArrayList用法。2.源碼分析。先用法後分析是爲了之後忘了查閱起來方便~~數組

ArrayList 基本用法

1.建立ArrayList對象markdown

//建立默認容量的數組列表(默認爲10)
ArrayList<E> a = new ArrayList<E>();

//建立容量爲initialCapacity的數組列表
ArrayList<E> a = new ArrayList<E>(int initialCapacity);

//用一個集合來初始化數值列表
ArrayList<E> a = new ArrayList<E>(Collection<? extends E> c);

Mark:

CapacitySize 時兩個不一樣的概念:
Size: ArrayList包含的元素個數。
capacity : ArrayList的容量(默認爲10)。它是一個大於或等於size的值。當它的值小於size是就會對ArrayList進行擴容實現可變長數組。簡單來講,capacity就是爲了實現可變長數組而設計的。 (具體分析請看下面的源碼分析)數據結構

2.ArrayList方法 app

向ArrayList添加元素

/***********a是上面建立的ArrayList對象***************/

//添加一個元素
a.add(E e);

//在特定的位置添加元素,index後面的元素所有向後移一位
a.add(int index, E e);

//在ArrayList 後面 添加集合
a.addAll(Collection<? extends E> c);

//從特定的位置添加集合
a.addAll(int index, Collection<? extends E> c);

獲取ArrayList的元素

//獲取特定索引的元素
a.get(int index);

Mark:

遍歷ArrayList: 通常使用for-each
for(E e : a)
   dosomething;


用for-each的主要優點:
相對傳統的for循環,for-each更簡潔,不容易出錯,並且沒有性能的損失函數


修改(更新)ArrayList的元素

//用e替換換index位置的元素
a.set(int index, E e);

Mark:

注意: 區分set與add的用法
add 是添加一個新的元素,要開闢新的空間來保存元素
set 是修改特定位置的元素,index位置必需要已經存在元素,不然會越界 源碼分析

刪除ArrayList的元素

//刪除索引爲index的元素,後面的元素全都向前移一位
a.remove(int index);

/*刪除在列表中和obj相等的第一個元素(首次出現),  
* 就作動做,保持列表原來的狀態
* 好比:a [1, 3, 7, 5, 7],執行a.remove((Integer)7)後
* 結果爲:[1, 3, 5, 7]
*/
a.remove(Object obj);

//刪除與集合c中元素相等的元素
a.removeAll(Collection<?> c);

//保留與集合c中元素相等的元素,而後把其餘元素刪除(與removeAll相反)
a.retainAll(Collection<?> c);  

//刪除ArrayList的全部元素,沒有removeAll();方法
a.clear();

ArrayList的其餘一些比較經常使用的方法

//獲取ArrayList的元素個數
a.size();

//判斷ArrayList是否包含某個元素
a.contains(Object o)

//判斷ArrayList是不是空列表([]),是就返回true
a.isEmpty();

//獲取ArrayList的子列表,(字串包含索引爲fromIndex的元素,不包含toIndex元素)
a.subList(int fromIndex, int toIndex);

//返回ArrayList的基本數組形式
a.toArray();

/**ArrayList克隆,調用方法後,會返回a的一份複製。它與普通的複製(=)不一樣 
*賦值後,兩個引用指向同一個對象,會相互影響  
*從ArrayList源碼可知,是開闢新的空間來新建一個對象,再把數據複製到新建的對象。
*因此,clone後會造成兩個不一樣的對象,所以不會相互影響。
*/
a.clone();

Mark:

a.remove(Object obj): 若是a是Integer的泛型列表在支持remove對象時要強制轉換爲Ingeger或者Object類型(參考上面例子),若是直接執行a.remove(7),就會調用 a.remove(int index)方法。性能


ArrayList源碼分析

首先,看看ArrayList定義的屬性

private static final int DEFAULT_CAPACITY = 10;  
private static final Object[] EMPTY_ELEMENTDATA = {};  
transient Object[] elementData;   
private int size;

DEFAULT_CAPACITY就是上面提到的默認容量大小,從屬性定義可知,容量的默認大小爲10。測試

EMPTY_ELEMENTDATA一個空的數組對象,主要用來與elementData作比較,elementData是否爲空spa

elementData最核心的一個屬性。它是ArrayList的廬山真面目,ArrayList之因此是可變長的,主要是ArrayList內部有elementData這個東東在搞鬼~~。 ArrayList可變長原理:當size大於capacity時,ArrayList就會調用擴容函數新建一個容量更大的elementData數組來代替原來的數組來實現擴容。
總的來講,ArrayList的可變長功能主要時圍繞着capacity與elementData來實現的 設計

實現可變長數組的主要方法:

public void ensureCapacityInternal(int minCapacity)
private void ensureExplicitCapacity(int minCapacity)
private void grow(int minCapacity) //核心方法

ensureCapacityInternal解析

/**
 * 此函數大概就叫內部容量肯定函數,主要是在每次添加元素這些增大size的
 * 動做裏調用,用來判斷需不須要增長capacity大小實現動態分配
 */  
private void ensureCapacityInternal(int minCapacity) {
   /**
    *這個判斷只要是針對使用new ArrayList<E>()構造對象設定,以避免頻繁的擴容。
    *若是if判斷爲真,那麼就是使用new ArrayList<E>()構造對象的.  
    *此刻就要選一個最大的容量做爲期待的最小容量,避免頻繁擴容
    *詳細解析看下面的例子!!
    */
    if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    //調用最終確認方法,需不須要擴容由此方法決定
    ensureExplicitCapacity(minCapacity);
}

if (elementData == EMPTY_ELEMENTDATA)做用解析

首先,咱們新建一個類(個人爲MyList),把ArrayList的源碼複製過來方便隨心所欲~~,而後把一些報錯除去。最後咱們修改下代碼:

// 把ensureCapacityInternal的if語句註釋掉,
/*if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }*/  
在**grow**方法裏添加一個輸出語句
     System.out.println("-----invoke grow()------------");

寫咱們的測試類:

public class Test {
public static void main(String[] args) {

     MyList<Integer> i = new MyList<Integer>();
     MyList<Integer> i2 = new MyList<Integer>(10);

    System.out.println("------使用new MyList<Integer>()------ ");
    i.add(1);
    i.add(1);
    i.add(1);
    i.add(1);

    System.out.println("------使用new MyList<Integer>(10)------ ");
    i2.add(1);
    i2.add(1);
    i2.add(1);
    i2.add(1);
}

}
輸出:

———使用new MyList()———
——-invoke grow()——————
——-invoke grow()——————
——-invoke grow()——————
——-invoke grow()——————
———使用new MyList(10)———

能夠看出,若是不是使用if判斷,使用new ArrayList()方法每次添加元素都會調用grow,而每次調用grow都會生成一個新的數組。因此爲了效率內存考慮要添加if判斷。

ensureExplicitCapacity方法

/**
 *此代碼功能很簡單,只是進行一個簡單的判斷,看是否須要擴容
 *若是須要的容量比實際的容量大就進行擴容
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

grow(核心方法)

/**
  *擴容的具體動做
  */
 private void grow(int minCapacity) {

    int oldCapacity = elementData.length;

    //定義擴容後的容量大小爲原來容量的3/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //若是擴容後的容量仍是比期待的容量小,那麼使用minCapacity爲擴容後的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    /*這裏實現了動態數組的特性。
     *跟蹤copyOf源碼能夠發現,實現方法:新建一個大小爲newCapacity數組,
     *而後把elementData以前的元素複製到新的數組,
     *最後把新數組返回f賦值給elementData以實現動態數組
     */
    elementData = Arrays.copyOf(elementData, newCapacity);
}

總結:

根據源碼分析,能夠知道:

  1. ArrayList本質上是一個數組。只不過是數組上包裝了一層華麗的外套而已。
  2. ArrayList動態數組功能主要思想是,每當數組的空間不夠時,ArrayList會自動把緩衝區(elementData,咱們叫他作緩衝區把~~)的數據複製到一個新的緩衝區裏,(大小通常爲原來的3/2),並令它覆蓋原來的緩衝區成爲本身的緩衝區。


經常使用方法分析

寫到這發現文章太長了,因此決定經常使用方法就寫幾個算了,不全寫了。經常使用方法的源碼都很簡單。大可能是對elementData這個數組操做返回而已。

add方法

public boolean add(E e) {
   //此方法上面已介紹
    ensureCapacityInternal(size + 1);  
    //操做elementData,把e添加到數組裏
    elementData[size++] = e;
    return true;
}

set方法

public E set(int index, E element) {
    //檢查是否越界,該函數代碼很簡單,就是簡單判斷index與size大小。
    rangeCheck(index);

    //如下代碼跟add對比下就知道他們的區別
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

總的來講,ArrayList的源碼不算難,主要部分實現動態數組那部分,其餘的方法主要仍是在操做elementData數組。感受ArrayList是一個很經常使用的數據結構,看懂源碼對之後使用ArrayList確定有很多幫助,並且解了ArrayList用起來也踏實:-D。

相關文章
相關標籤/搜索