java.util.ArrayList 是 Java 集合框架中使用頻率最高的動態數組實現。本文從源碼級剖析其底層結構、擴容策略、隨機訪問優勢、序列化機制,並通過實測數據對比三種遍歷效率,最後重點講解 toArray() 類型轉換陷阱與正確姿勢。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable { }
transient Object[] elementData + int size。RandomAccess → 標記支持快速隨機訪問(O(1) 下標直達)。Cloneable、Serializable → 支持淺克隆與標準序列化。| 構造函數 | 行為 |
|---|---|
ArrayList() | 默認容量 10,elementData = new Object[10] |
ArrayList(int initialCapacity) | 指定初始容量,負數拋異常 |
ArrayList(Collection<? extends E> c) | 複製構造,容量 = 集合大小,數組類型校正為 Object[] |
ensureCapacity 與 1.5 倍增長public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object[] oldData = elementData;
int newCapacity = (oldCapacity * 3) / 2 + 1; // 1.5倍 + 1
if (newCapacity < minCapacity) newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
add(E e) 內部調用 ensureCapacity(size + 1)。Arrays.copyOf → 原生數組複製,性能較好但頻繁擴容仍有開銷。initialCapacity 避免擴容。get/set 極致 O(1)public E get(int index) {
rangeCheck(index);
return (E) elementData[index]; // 直接下標訪問
}
public E set(int index, E element) {
rangeCheck(index);
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
// 指定位置插入
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
// 指定位置刪除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
elementData[--size] = null; // 釋放引用助 GC
return oldValue;
}
結論:尾部追加 O(1)(均攤),頭部/中間插刪 O(n) 需搬移後續元素。
private void writeObject(ObjectOutputStream s) throws IOException {
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(elementData.length); // 寫入數組容量
for (int i = 0; i < size; i++) // 寫入前 size 個元素
s.writeObject(elementData[i]);
if (modCount != expectedModCount) throw new ConcurrentModificationException();
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];
for (int i = 0; i < size; i++)
a[i] = s.readObject();
}
只序列化 size 個有效元素,不序列化數組尾部空槽,節省空間。
public Object clone() {
try {
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
iteratorThroughRandomAccess:3 ms // for (int i=0; i
結論:實現RandomAccess的 ArrayList,**下標隨機訪問最快**,for-each 次之,顯式 Iterator 最慢。實際開發優先for (int i=0; i或 for (E e : list)。
Object[] toArray(); // 返回 Object[],無法直接轉型
<T> T[] toArray(T[] a); // 返回 T[],類型安全
toArray() 會拋 ClassCastExceptionList<Integer> list = new ArrayList<>();
list.add(1);
Integer[] arr = (Integer[]) list.toArray(); // 拋 java.lang.ClassCastException
// 原因:toArray() 返回 new Object[size],Java 不支持數組向下轉型 (Object[] → Integer[])
// 姿勢一:預分配精確大小數組
public static Integer[] toArray1(List<Integer> list) {
Integer[] arr = new Integer[list.size()];
list.toArray(arr);
return arr;
}
// 姿勢二:傳入零長度數組(最常用、推薦,JDK 內部優化會精確分配)
public static Integer[] toArray2(List<Integer> list) {
return list.toArray(new Integer[0]);
}
// 姿勢三:預分配後再轉型(冗餘,不推薦)
public static Integer[] toArray3(List<Integer> list) {
Integer[] arr = new Integer[list.size()];
return (Integer[]) list.toArray(arr);
}
最佳實踐:統一使用 list.toArray(new T[0])。JDK 6+ 會優化零長度數組分配,性能與預分配持平,代碼最簡潔。
import java.util.*;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1"); list.add("2"); list.add("3"); list.add("4");
// 指定位置插入
list.add(0, "5"); // [5, 1, 2, 3, 4]
System.out.println("首元素: " + list.get(0)); // 5
// 刪除元素
list.remove("3"); // 刪除值為 "3" 的首個元素
System.out.println("size: " + list.size()); // 4
// 判斷包含
System.out.println("contains 3: " + list.contains("3")); // false
// 修改元素
list.set(1, "10"); // 索引 1 設為 "10"
System.out.println("list: " + list); // [5, 10, 2, 4]
// Iterator 遍歷
System.out.println("\nIterator 遍歷:");
for (Iterator<String> it = list.iterator(); it.hasNext(); )
System.out.println("next: " + it.next());
// 安全轉數組
String[] arr = list.toArray(new String[0]);
System.out.println("\ntoArray 結果:");
for (String s : arr) System.out.println("str: " + s);
// 清空
list.clear();
System.out.println("isEmpty: " + list.isEmpty()); // true
}
}
| 場景 | 推薦 | 理由 |
|---|---|---|
| 單線程、高頻隨機訪問、尾部追加 | ArrayList | O(1) 訪問、緊湊內存、無同步開銷 |
| 高頻頭部/中間插刪 | LinkedList / ArrayDeque | 避免數組搬移 |
| 多線程讀多寫少 | CopyOnWriteArrayList | 遍歷無鎖、寫時復制 |
| 多線程讀寫均衡 | Collections.synchronizedList / 手動加鎖 | 細粒度控制 |
| 預估大數據量 | new ArrayList<>(estimatedSize) | 避免多次擴容複製 |
for (int i=0; i 或 for (E e : list)。 list.toArray(new T[0])。Iterator.remove()。java.util.concurrent 包對應類。