
1. 為什么需要動態數組在Java編程中數組是最基礎的數據結構之一。但原生數組有個致命缺陷長度固定。一旦創建就無法動態擴展或收縮。想象你正在開發一個用戶管理系統最初分配了100個用戶的空間但當用戶增長到101個時系統就會崩潰。這就是ArrayList誕生的背景。ArrayList是Java集合框架中最常用的動態數組實現。它內部維護了一個Object[]數組當容量不足時自動擴容通常是1.5倍。這種設計既保留了數組隨機訪問的高效性O(1)時間復雜度又提供了動態調整的靈活性。實際開發中90%需要數組的場景都會優先選擇ArrayList。除非對內存有極端要求否則固定長度的原生數組很少直接使用。2. ArrayList核心實現原理2.1 底層數據結構剖析打開ArrayList源碼你會發現這個關鍵字段transient Object[] elementData;這就是存儲數據的核心數組。transient關鍵字表示序列化時會忽略這個字段ArrayList自定義了序列化邏輯來優化空間。擴容機制是ArrayList最精妙的部分。當調用add()方法且當前size elementData.length時觸發private void grow(int minCapacity) { int oldCapacity elementData.length; int newCapacity oldCapacity (oldCapacity 1); // 1.5倍 if (newCapacity - minCapacity 0) newCapacity minCapacity; elementData Arrays.copyOf(elementData, newCapacity); }這里有個性能陷阱頻繁擴容會導致大量數組拷貝。初始化時如果能預估大小建議使用帶初始容量的構造函數ListString list new ArrayList(1000); // 直接分配1000容量2.2 線程安全問題ArrayList不是線程安全的。一個經典錯誤場景ListString list new ArrayList(); // 線程A list.add(A); // 線程B list.add(B);當多線程并發修改時可能導致數據覆蓋ArrayIndexOutOfBoundsException擴容時數組狀態不一致解決方案使用Collections.synchronizedList包裝改用CopyOnWriteArrayList讀多寫少場景在方法內部new ArrayList線程隔離3. 必須掌握的API實戰3.1 基礎CRUD操作ArrayListString fruits new ArrayList(); // 增 fruits.add(Apple); // 尾部添加 fruits.add(0, Banana); // 指定位置插入 // 刪 fruits.remove(0); // 按索引刪除 fruits.remove(Apple); // 按元素刪除 // 改 fruits.set(0, Orange); // 替換指定位置元素 // 查 String first fruits.get(0); boolean hasApple fruits.contains(Apple);3.2 批量操作技巧// 批量添加 fruits.addAll(Arrays.asList(Grape, Peach)); // 批量刪除交集 fruits.removeAll(Arrays.asList(Grape, Peach)); // 保留交集 fruits.retainAll(Arrays.asList(Apple, Orange)); // 清空 fruits.clear();3.3 迭代器高級用法// 基本迭代 IteratorString it fruits.iterator(); while(it.hasNext()) { System.out.println(it.next()); } // 刪除元素的安全方式 IteratorString it fruits.iterator(); while(it.hasNext()) { if(it.next().equals(Apple)) { it.remove(); // 唯一線程安全的刪除方式 } }4. 性能優化實戰4.1 初始化容量優化測試對比// 不指定初始容量 long start System.currentTimeMillis(); ListInteger list1 new ArrayList(); for (int i 0; i 1000000; i) { list1.add(i); } System.out.println(默認容量耗時 (System.currentTimeMillis() - start)); // 指定足夠容量 start System.currentTimeMillis(); ListInteger list2 new ArrayList(1000000); for (int i 0; i 1000000; i) { list2.add(i); } System.out.println(預分配容量耗時 (System.currentTimeMillis() - start));實測結果可能相差50%以上4.2 遍歷性能對比測試三種遍歷方式// 1. for循環 for(int i0; ilist.size(); i) { String s list.get(i); } // 2. 增強for循環 for(String s : list) {} // 3. forEachlambda list.forEach(s - {});在ArrayList中傳統for循環最快直接數組訪問增強for循環會生成Iterator對象forEach有lambda開銷4.3 空間優化技巧ArrayList刪除元素后不會自動縮容需要手動trimToSize()list.removeIf(s - s.startsWith(A)); // 批量刪除 list.trimToSize(); // 釋放多余空間5. 常見坑點與解決方案5.1 并發修改異常ListString list new ArrayList(Arrays.asList(A,B,C)); for(String s : list) { if(s.equals(B)) { list.remove(s); // 拋出ConcurrentModificationException } }正確做法使用Iterator.remove()使用CopyOnWriteArrayList使用fori循環倒序刪除5.2 泛型類型擦除ListInteger intList new ArrayList(); List rawList intList; rawList.add(String); // 編譯通過運行時報錯解決方案避免使用原生類型使用SuppressWarnings(unchecked)要謹慎考慮使用ImmutableList5.3 自定義對象處理class Person { String name; // 必須重寫equals和hashCode Override public boolean equals(Object o) { if(this o) return true; if(!(o instanceof Person)) return false; return name.equals(((Person)o).name); } } ListPerson people new ArrayList(); people.add(new Person(Alice)); boolean contains people.contains(new Person(Alice)); // 依賴equals實現6. 進階應用場景6.1 實現棧結構class SimpleStackE { private ArrayListE list new ArrayList(); public void push(E item) { list.add(item); } public E pop() { if(list.isEmpty()) throw new EmptyStackException(); return list.remove(list.size()-1); } }6.2 數據分頁處理public static T ListT getPage(ListT source, int page, int size) { int fromIndex (page - 1) * size; if(fromIndex source.size()) return Collections.emptyList(); int toIndex Math.min(fromIndex size, source.size()); return source.subList(fromIndex, toIndex); }6.3 與Stream API結合ListString filtered list.stream() .filter(s - s.length() 3) .sorted() .collect(Collectors.toCollection(ArrayList::new));7. 面試高頻問題解析7.1 ArrayList vs LinkedList從四個維度對比隨機訪問ArrayList O(1) vs LinkedList O(n)頭插刪除ArrayList O(n) vs LinkedList O(1)內存占用ArrayList更緊湊 vs LinkedList節點開銷迭代性能ArrayList緩存友好 vs LinkedList指針跳轉7.2 擴容機制細節默認初始容量10擴容公式newCapacity oldCapacity (oldCapacity 1)最大容量Integer.MAX_VALUE - 8部分VM保留頭信息精確控制擴容ensureCapacity(int minCapacity)7.3 fail-fast機制ArrayList迭代器通過modCount檢測并發修改final void checkForComodification() { if (modCount ! expectedModCount) throw new ConcurrentModificationException(); }這是快速失敗(fail-fast)設計強調盡早暴露錯誤。8. 最佳實踐總結初始化盡量預估容量避免多次擴容線程安全多線程環境使用CopyOnWriteArrayList或同步包裝遍歷刪除只使用Iterator.remove()空間管理大數據量刪除后調用trimToSize()性能敏感優先用fori而不是迭代器API選擇contains()比indexOf()更語義化subList()返回的是視圖修改會影響原列表版本兼容注意JDK8和后續版本在stream處理上的優化差異實際項目中我曾用ArrayList處理過百萬級數據導入。關鍵經驗是提前分批次處理每批用固定容量的ArrayList處理完立即釋放。這比用單個超大ArrayList內存效率高30%以上。