首先,我們來看ArrayList的繼承關係如下,從表面上我們可以看到它支持抽象對象的方法
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
我們來看看都繼承了哪些方法
AbstractList 一個抽象類,裏面會有一些常用方法的實現
List接口必須實現方法
randomAccess是一個空接口,起到一個標識的作用,在AbstractList會有用到,這裏暫不討論
Colneable 與randomAccess類似也是一個空接口,提供了Object.colne()方法
Serializable 接口以啓用其序列化功能
ArrayList用到了兩個全局變量,對於多線程稍微瞭解一點,也應該能夠知道,從這裏就可以看出ArrayList也出現併發問題,非線程安全類
Object[] elementData 數組類型
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
List list = new ArrayList();//這是我們常用的創建方式,我們來看看內部會發生什麼?
默認對象屬性爲10,爲數組形式
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];// <span style="color:#FF0000;">數組類型</span>
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10); // <span style="color:#FF0000;">默認10個元素</span>
}
接下來進入主題,來看看它的添加方法
<span style="font-size:14px;">/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}</span>
重點來了,我們知道初始元素個數爲10,那麼如果滿了會發生什麼呢
<span style="font-size:14px;">private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}</span>
<span style="font-size:14px;"> </span><pre name="code" class="java"><span style="font-size:14px;"> private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;</span>
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; 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); }
從這裏我們可以看到,每次的擴容增量爲 (1 + 0.5) * oldCapacity,最大爲
<pre name="code" class="java"><span style="font-size:14px;">Integer.MAX_VALUE</span>
並且每次的擴容會重新創建一個對象
<span style="font-size:14px;"> public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}</span>
設想一下,如果每次添加一個元素,一直添加10萬百萬個元素,會創建多少個對象
最簡單的優化方法,在創建list的時候,我們是否可以預估一下預計的個數呢?每次添加元素之前 我們是否可以預估一下要擴容元素的個數呢?
<span style="font-size:14px;">ArrayList list = new ArrayList(100);// 更改默認值100
list.ensureCapacity(1000); // 預估擴容1000</span>
如果有其他的好的擴容方法,歡迎討論
測試如下
public static void main(String[] args) throws IOException {
Runtime run = Runtime.getRuntime();
run.gc();
System.out.println("time: " + (new Date()));
// 獲取開始時內存使用量
long startMem = run.totalMemory() - run.freeMemory();
System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + startMem);
ArrayList list = new ArrayList();
int size = 10000000;
// list.ensureCapacity(size); // 是否擴容
int i = 0;
try {
for (; i < size; i++) {
list.add(i);
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println("i= " + i);
}
System.out.println("time: " + (new Date()));
long endMem = run.totalMemory() - run.freeMemory();
System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + endMem);
System.out.println("memory difference:" + (endMem - startMem));
}
輸出內存差異結果結果:
a.不擴容
time: Tue Aug 23 16:38:49 CST 2016
memory> total:18350080 free:16858832 used:1491248
time: Tue Aug 23 16:38:53 CST 2016
memory> total:291962880 free:14030184 used:277932696
memory difference:276441448
b.擴容
time: Tue Aug 23 16:41:47 CST 2016
memory> total:18612224 free:17107928 used:1504296
time: Tue Aug 23 16:41:50 CST 2016
memory> total:296026112 free:94840808 used:201185304
memory difference:199681008