java-基礎-ArrayList原理解析

前言:

一.在其他播客上看到下面這段話,可以說是總結的非常精闢了。讀者們可以仔細品味:

ArrayList和LinkedList在性能上各 有優缺點,都有各自所適用的地方,總的說來可以描述如下:
1.對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。對 ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是 統一的,分配一個內部Entry對象。

2.在ArrayList的 中間插入或刪除一個元素意味着這個列表中剩餘的元素都會被移動;而在LinkedList的中間插入或刪除一個元素的開銷是固定的。

3.LinkedList不 支持高效的隨機元素訪問。

4.ArrayList的空 間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間


可以這樣說:當操作是在一列 數據的後面添加數據而不是在前面或中間,並且需要隨機地訪問其中的元素時,使用ArrayList會提供比較好的性能;當你的操作是在一列數據的前面或中 間添加或刪除數據,並且按照順序訪問其中的元素時,就應該使用LinkedList了。
所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那麼使用Vector或ArrayList都可以。如果是對其它指定位置的插入、刪除操作,最好選擇LinkedList


 

二:下面我從源碼上分析ArrayList:

  屬性:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 序列化 ID
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * ArrayList 默認的數組容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    // 一個默認的空數組
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 在調用無參構造方法的時候使用該數組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

  
    // 存儲 ArrayList 元素的數組
    // transient 關鍵字這裏簡單說一句,被它修飾的成員變量無法被 Serializable 序列化 
    transient Object[] elementData; // non-private to simplify nested class access

    // ArrayList 的大小,也就是 elementData 包含的元素個數
    private int size;
}

  實現了 Serializable 是序列化接口,因此它支持序列化,能夠通過序列化傳輸。
  實現了 Cloneable 接口,能被克隆。
  實現了Iterable 接口,可以被迭代器遍歷
  實現了 Collection ,擁有集合操作的方法
  實現了 List 接口,擁有增刪改查等方法
  實現了 RandomAccess 隨機訪問接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問。

構造方法:

可以看到elementData 數組 就是ArrayList用來存儲數據的數組。

// 指定大小的構造方法,如果傳入的是 0 ,直接使用 EMPTY_ELEMENTDATA
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
// 調用該構造方法構造一個默認大小爲 10 的數組,但是此時大小未指定,
// 還是空的,在第一次 add 的時候指定
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 傳入一個集合類
// 首先直接利用Collection.toArray()方法得到一個對象數組,並賦值給elementData 
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray 出錯的時候,使用Arrays.copyOf 生成一個新數組賦值給 elementData
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //如果集合c元素數量爲0,則將空數組EMPTY_ELEMENTDATA賦值給elementData 
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

方法:

我們重點說明 addAll 方法,涉及到ArrayList的數組擴容原理。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

1. add方法調用ensureCapacityInternal()方法,可以看到如果初始大小爲{},則數組默認大小爲10,繼續調用

ensureExplicitCapacity()方法。

private void ensureCapacityInternal(int minCapacity) {
    // 如果創建 ArrayList 時候,使用的無參的構造方法,那麼就取默認容量 10 和最小需要的容量(當前 size + 1 )中大的一個確定需要的容量。
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

2. 主要代碼是 :

 int newCapacity = oldCapacity + (oldCapacity >> 1);

 這裏表示新的數組大小爲 原數組大小 + 原數組大小向右位移 1 位。後面我專門做右位移運算講解。

private void ensureExplicitCapacity(int minCapacity) {
    // 修改 +1 
    modCount++;
    // 如果 minCapacity 比當前容量大, 就執行grow 擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // 拿到當前的容量
    int oldCapacity = elementData.length;
    // oldCapacity >> 1 意思就是 oldCapacity/2,所以新容量就是增加 1/2.
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新容量小於,需要最小擴容的容量,以需要最小容量爲準擴容
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新容量大於允許的最大容量,則以 Inerger 的最大值進行擴容
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 使用 Arrays.copyOf 函數進行擴容。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 允許的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

ArrayList的數組擴容原理介紹:

到這裏我們可以看到 當我們創建一個ArrayList集合時。elementData 數組初始大小爲0,當我們添加一個元素時elementData 大小爲默認大小10,集合大小超過默認大小時,elementData 爲之前的1.5倍大小,實際上是原數組大小 + 原數組大小右移1位。

下面我們來做一次計算:當我不停的往ArrayList 添加數據時:

List<String> list = new ArrayList<String>();
    	System.out.println(list);
    	for(int i = 0; i<100; i++) {
    		list.add("a");	
    	}
    	
    	System.out.println(list);

List<String> list = new ArrayList<String>();  //elementData 大小爲0

第一次添加時  //elementData 大小爲10

list.size()>10時   //  10的二進制大小 爲  1010 右移一位大小爲  0101,二級制 0101 的大小爲5,所以elementData 大小爲15

list.size()>15時  //   10的二進制大小 爲  1111 右移一位大小爲  0111,二級制 0111 的大小爲7,所以elementData 大小爲22

所以 當我 創建一個ArrayList時

當我add一個數據時 elementData大小爲10

當集合大小超過10的時候,elementData大小爲15

 當集合大小超過15時,elementData大小爲22

 至此,驗證成功。

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