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