態(tài)數(shù)組原理與性能優(yōu)化實(shí)戰(zhàn))
1. 為什么需要?jiǎng)討B(tài)數(shù)組在Java編程中數(shù)組是最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)之一。但原生數(shù)組有個(gè)致命缺陷長(zhǎng)度固定。一旦創(chuàng)建就無(wú)法動(dòng)態(tài)擴(kuò)展或收縮。想象你正在開(kāi)發(fā)一個(gè)用戶(hù)管理系統(tǒng)最初分配了100個(gè)用戶(hù)的空間但當(dāng)用戶(hù)增長(zhǎng)到101個(gè)時(shí)系統(tǒng)就會(huì)崩潰。這就是ArrayList誕生的背景。ArrayList是Java集合框架中最常用的動(dòng)態(tài)數(shù)組實(shí)現(xiàn)。它內(nèi)部維護(hù)了一個(gè)Object[]數(shù)組當(dāng)容量不足時(shí)自動(dòng)擴(kuò)容通常是1.5倍。這種設(shè)計(jì)既保留了數(shù)組隨機(jī)訪問(wèn)的高效性O(shè)(1)時(shí)間復(fù)雜度又提供了動(dòng)態(tài)調(diào)整的靈活性。實(shí)際開(kāi)發(fā)中90%需要數(shù)組的場(chǎng)景都會(huì)優(yōu)先選擇ArrayList。除非對(duì)內(nèi)存有極端要求否則固定長(zhǎng)度的原生數(shù)組很少直接使用。2. ArrayList核心實(shí)現(xiàn)原理2.1 底層數(shù)據(jù)結(jié)構(gòu)剖析打開(kāi)ArrayList源碼你會(huì)發(fā)現(xiàn)這個(gè)關(guān)鍵字段transient Object[] elementData;這就是存儲(chǔ)數(shù)據(jù)的核心數(shù)組。transient關(guān)鍵字表示序列化時(shí)會(huì)忽略這個(gè)字段ArrayList自定義了序列化邏輯來(lái)優(yōu)化空間。擴(kuò)容機(jī)制是ArrayList最精妙的部分。當(dāng)調(diào)用add()方法且當(dāng)前size elementData.length時(shí)觸發(fā)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); }這里有個(gè)性能陷阱頻繁擴(kuò)容會(huì)導(dǎo)致大量數(shù)組拷貝。初始化時(shí)如果能預(yù)估大小建議使用帶初始容量的構(gòu)造函數(shù)ListString list new ArrayList(1000); // 直接分配1000容量2.2 線(xiàn)程安全問(wèn)題ArrayList不是線(xiàn)程安全的。一個(gè)經(jīng)典錯(cuò)誤場(chǎng)景ListString list new ArrayList(); // 線(xiàn)程A list.add(A); // 線(xiàn)程B list.add(B);當(dāng)多線(xiàn)程并發(fā)修改時(shí)可能導(dǎo)致數(shù)據(jù)覆蓋ArrayIndexOutOfBoundsException擴(kuò)容時(shí)數(shù)組狀態(tài)不一致解決方案使用Collections.synchronizedList包裝改用CopyOnWriteArrayList讀多寫(xiě)少場(chǎng)景在方法內(nèi)部new ArrayList線(xiàn)程隔離3. 必須掌握的API實(shí)戰(zhàn)3.1 基礎(chǔ)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 迭代器高級(jí)用法// 基本迭代 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(); // 唯一線(xiàn)程安全的刪除方式 } }4. 性能優(yōu)化實(shí)戰(zhàn)4.1 初始化容量?jī)?yōu)化測(cè)試對(duì)比// 不指定初始容量 long start System.currentTimeMillis(); ListInteger list1 new ArrayList(); for (int i 0; i 1000000; i) { list1.add(i); } System.out.println(默認(rèn)容量耗時(shí) (System.currentTimeMillis() - start)); // 指定足夠容量 start System.currentTimeMillis(); ListInteger list2 new ArrayList(1000000); for (int i 0; i 1000000; i) { list2.add(i); } System.out.println(預(yù)分配容量耗時(shí) (System.currentTimeMillis() - start));實(shí)測(cè)結(jié)果可能相差50%以上4.2 遍歷性能對(duì)比測(cè)試三種遍歷方式// 1. for循環(huán) for(int i0; ilist.size(); i) { String s list.get(i); } // 2. 增強(qiáng)for循環(huán) for(String s : list) {} // 3. forEachlambda list.forEach(s - {});在ArrayList中傳統(tǒng)for循環(huán)最快直接數(shù)組訪問(wèn)增強(qiáng)for循環(huán)會(huì)生成Iterator對(duì)象forEach有l(wèi)ambda開(kāi)銷(xiāo)4.3 空間優(yōu)化技巧ArrayList刪除元素后不會(huì)自動(dòng)縮容需要手動(dòng)trimToSize()list.removeIf(s - s.startsWith(A)); // 批量刪除 list.trimToSize(); // 釋放多余空間5. 常見(jiàn)坑點(diǎn)與解決方案5.1 并發(fā)修改異常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循環(huán)倒序刪除5.2 泛型類(lèi)型擦除ListInteger intList new ArrayList(); List rawList intList; rawList.add(String); // 編譯通過(guò)運(yùn)行時(shí)報(bào)錯(cuò)解決方案避免使用原生類(lèi)型使用SuppressWarnings(unchecked)要謹(jǐn)慎考慮使用ImmutableList5.3 自定義對(duì)象處理class Person { String name; // 必須重寫(xiě)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)); // 依賴(lài)equals實(shí)現(xiàn)6. 進(jìn)階應(yīng)用場(chǎng)景6.1 實(shí)現(xiàn)棧結(jié)構(gòu)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 數(shù)據(jù)分頁(yè)處理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結(jié)合ListString filtered list.stream() .filter(s - s.length() 3) .sorted() .collect(Collectors.toCollection(ArrayList::new));7. 面試高頻問(wèn)題解析7.1 ArrayList vs LinkedList從四個(gè)維度對(duì)比隨機(jī)訪問(wèn)ArrayList O(1) vs LinkedList O(n)頭插刪除ArrayList O(n) vs LinkedList O(1)內(nèi)存占用ArrayList更緊湊 vs LinkedList節(jié)點(diǎn)開(kāi)銷(xiāo)迭代性能ArrayList緩存友好 vs LinkedList指針跳轉(zhuǎn)7.2 擴(kuò)容機(jī)制細(xì)節(jié)默認(rèn)初始容量10擴(kuò)容公式newCapacity oldCapacity (oldCapacity 1)最大容量Integer.MAX_VALUE - 8部分VM保留頭信息精確控制擴(kuò)容ensureCapacity(int minCapacity)7.3 fail-fast機(jī)制ArrayList迭代器通過(guò)modCount檢測(cè)并發(fā)修改final void checkForComodification() { if (modCount ! expectedModCount) throw new ConcurrentModificationException(); }這是快速失敗(fail-fast)設(shè)計(jì)強(qiáng)調(diào)盡早暴露錯(cuò)誤。8. 最佳實(shí)踐總結(jié)初始化盡量預(yù)估容量避免多次擴(kuò)容線(xiàn)程安全多線(xiàn)程環(huán)境使用CopyOnWriteArrayList或同步包裝遍歷刪除只使用Iterator.remove()空間管理大數(shù)據(jù)量刪除后調(diào)用trimToSize()性能敏感優(yōu)先用fori而不是迭代器API選擇contains()比indexOf()更語(yǔ)義化subList()返回的是視圖修改會(huì)影響原列表版本兼容注意JDK8和后續(xù)版本在stream處理上的優(yōu)化差異實(shí)際項(xiàng)目中我曾用ArrayList處理過(guò)百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)入。關(guān)鍵經(jīng)驗(yàn)是提前分批次處理每批用固定容量的ArrayList處理完立即釋放。這比用單個(gè)超大ArrayList內(nèi)存效率高30%以上。