ArrayList源碼中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別

2018年7月22日09:54:17java

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

寫在前面的話:程序員

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

言歸正傳,《Effective Java》第二版<i>第47條:瞭解和使用類庫</i>中有這麼一句話:<b>每一個程序員都應該熟悉java.lang、java.util,某種程度上還有java.io中的內容。</b>而後我就從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。即:ui

// 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)方法:this

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在添加第一個元素時,<i>最小的容量</i>是默認大小10。那有參構造函數建立的空實例al一、al2在經過add(E e)添加元素的時候是怎麼樣的呢?al一、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》第二版<i>第43條:返回零長度的數組或集合,而不是null</i>。難道由於這個建議讓ArrayList空實例增長了,因此類庫的編寫者做出了這個優化,哈哈。

總結之EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別:<b>EMPTY_ELEMENTDATA是爲了優化建立ArrayList空實例時產生沒必要要的空數組,使得全部ArrayList空實例都指向同一個空數組。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是爲了確保無參構成函數建立的實例在添加第一個元素時,<i>最小的容量</i>是默認大小10。</b>

2018年7月22日16:48:05

相關文章
相關標籤/搜索