集合基本上面試時面試必問的,我一個同事曾經面試時就被問過集合的默認長度是多少。下面我們就ArrayList集合的長度來說說指定初始容量的事。
List<Persion> list = new ArrayList<Persion>();
相信大部分在使用集合是,都是類似的聲明一個集合,然後用add、remove等方法對集合進行操作,而且因爲它是自動管理長度的,所以不用我們特別費心超長的問題,這確實是一個非常好的優點,但也有我們必須要注意的事項。下面我以ArrayList爲例深入瞭解下java是如何實現自動長度的動態管理,從add方法開始,代碼如下(我的是jdk1.7):
public boolean add(E paramE)
{
//擴展長度
ensureCapacityInternal(this.size + 1);
//追加元素
this.elementData[(this.size++)] = paramE;
return true;
}
我們知道ArrayList是一個大小可變的,但它在底層使用的是數組存儲(也就是elementData變量),而且數組是定長的,要實行動態長度必然要進行長度的擴展,代碼如下:
private void ensureCapacityInternal(int paramInt) {
this.modCount += 1;
if (paramInt - this.elementData.length > 0)
grow(paramInt);
}
private void grow(int paramInt)
{
//上次(原始)定義的數組長度
int i = this.elementData.length;
int j = i + (i >> 1);
if (j - paramInt < 0)
j = paramInt;
if (j - 2147483639 > 0) {
j = hugeCapacity(paramInt);
}
this.elementData = Arrays.copyOf(this.elementData, j);
}
private static int hugeCapacity(int paramInt) {
if (paramInt < 0)
throw new OutOfMemoryError();
return paramInt > 2147483639 ? 2147483647 : 2147483639;
}
我們看到新數組的長度計算方法,並不是增加一個元素,elementData 的長度就加1,而是在達到elementData 長度的臨界點時,纔將elementData 擴容1.5倍(i + (i >> 1))右移1位相當於除以2,這樣有什麼好處呢?避免了多次調用copyOf方法的性能開銷,否則沒加一個元素都要擴容一次,那性能將會非常糟糕。
知道了擴容原則,還有一個問題:elementData 的默認長度是多少呢?答案是10,比如new ArrayList(),則elementData 的初始長度是10.代碼如下:
//無參構造,通常用的最多的就是這個
public ArrayList()
{
//指定數組長度的有參構造
this(10);
}
//指定數組長度的有參構造
public ArrayList(int paramInt)
{
if (paramInt < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
}
this.elementData = new Object[paramInt];
}
默認初始化時聲明瞭一個長度爲10的數組,在通過add方法增加弟11個元素時,ArrayList類就自動擴展了,新的elementData 數組長度是10+(10>>1),也就是15,可以看出,如果不設置初始容量,系統就按照1.5倍的規則擴容,每次擴容都是一次數組的拷貝,如果數據量很大,這樣的拷貝會非常耗費資源,而且效率非常低下。如果我們已經知道一個ArrayList的可能長度,然後對ArrayList設置一個初始容量則可以顯著提高系統性能。