ArrayList源碼中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別


2018年7月22日09:54:17

JDK 1.8.0_162 ArrayList源碼中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別

寫在前面的話:

關於閱讀源碼:剛開始學習的時候,覺得閱讀源碼是多么遙遠的事情,但是不知不覺已經畢業一年了,自己的進步不多。華羅庚說,“自學,不怕起點低,就怕不到底”。閱讀源碼應該是比較“底”了吧,哈哈。閱讀源碼,在面試官問你這個問題:“你讀過Java源碼嗎”的時候,你可以拍着胸口回答他:“讀過!!!”。Last but not least,就是可以裝逼:我已經讀過Java源碼了。(雖然不知道自己收獲了多少)

言歸正傳,《Effective Java》第二版第47條:了解和使用類庫中有這么一句話:每個程序員都應該熟悉java.lang、java.util,某種程度上還有java.io中的內容。然后我就從java.util開始讀了。

本文只是討論JDK 1.8.0_162中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別,關於源碼詳細解讀請Google。

在ArrayList中有關EMPTY_ELEMENTDATA(下文用EE代替)和DEFAULTCAPACITY_EMPTY_ELEMENTDATA(下文用DEE代替)的聲明定義如下:

/**
 * Shared empty array instance used for empty instances.
 * 用於ArrayList空實例的共享空數組實例
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 * 用於默認大小空實例的共享空數組實例。我們將this(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
 * 和EMPTY_ELEMENTDATA區別開來,以便在添加第一個元素時知道要膨脹多少。
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

這兩個類常量EE和DEE都是表示空數組,只是名字不一樣而已。

三個構造函數:

/**
 * 有參
 */
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);
    }
}

/**
 * 無參
 */
public ArrayList() {
	// 這里
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 參數為集合
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array. 這里
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

其中無參構造器創建的實例al的elementData是DEE,有參構造函數創建的空實例al1和al2的elementData是EE。即:

// elementData = DEE
ArrayList<String> al = new ArrayList<String>();

// elementData = EE
ArrayList<String> al1 = new ArrayList<String>(0);
ArrarList<String> al2 = new ArrayList<String>(al1)

接下來看看add(E e)方法:

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

private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 當第一次調用add(E e)方法的時候,判讀是不是無參構造函數創建的對象,如果是,
	// 將DEFAULT_CAPACITY即10作為ArrayList的容量,此時minCapacity = 1
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

其他add方法如:add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)中都有ensureCapacityInternal(int minCapacity)方法,確保無參構成函數創建的實例al在添加第一個元素時,最小的容量是默認大小10。那有參構造函數創建的空實例al1、al2在通過add(E e)添加元素的時候是怎么樣的呢?al1、al2容量增長是這樣子的:0->1->2->3->4->6->9->13...,這樣的增長是很慢的。具體擴容方式:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 新容量為舊容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

問題:兩個類常量都是表示空數組,為什么要用兩個呢?在Java7中只有一個類常量表示空數組,就是EE。Java8中添加了DEE代替了EE。

在Java7中ArrayList的構造函數:

public ArrayList(int initialCapacity) {
   super();
   if (initialCapacity < 0)
       throw new IllegalArgumentException("Illegal Capacity: "+
                                          initialCapacity);
   this.elementData = new Object[initialCapacity];
}

public ArrayList() {
   super();
   this.elementData = EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> c) {
   elementData = c.toArray();
   size = elementData.length;
   // c.toArray might (incorrectly) not return Object[] (see 6260652)
   if (elementData.getClass() != Object[].class)
       elementData = Arrays.copyOf(elementData, size, Object[].class);
}

完全就是DEE代替了EE。那EE干什么去了,看一下構造函數中EE安排在哪里了?都是在判斷容量為空的情況下,賦值給elementData。Java7中如果容量是0的話,會創建一個空數組,賦值給elementData:this.elementData = new Object[initialCapacity];elementData = Arrays.copyOf(elementData, size, Object[].class);。如果一個應用中有很多這樣ArrayList空實例的話,就會有很多的空數組,無疑EE是為了優化性能,所有ArrayList空實例都指向同一個空數組。問題解決。

題外話:《Effective Java》第二版第43條:返回零長度的數組或集合,而不是null。難道因為這個建議讓ArrayList空實例增加了,所以類庫的編寫者作出了這個優化,哈哈。

總結之EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別:EMPTY_ELEMENTDATA是為了優化創建ArrayList空實例時產生不必要的空數組,使得所有ArrayList空實例都指向同一個空數組。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是為了確保無參構成函數創建的實例在添加第一個元素時,最小的容量是默認大小10。

2018年7月22日16:48:05


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM