在 Java 開發中,數組與集合是最基礎也最常用的數據結構。雖然集合框架功能豐富,但數組在性能敏感場景下仍有不可替代的優勢。本文總結 9 大核心最佳實踐,助你寫出高效、健壯的代碼。
核心觀點:數組存儲基本類型,避免裝箱/拆箱開銷;集合底層也是數組實現,但存儲包裝類型。
// 基本類型數組:連續內存,無對象頭開銷
int[] intArray = new int[1000000];
// 集合:每個元素都是 Integer 對象,佔用更多內存,訪問需拆箱
List<Integer> intList = new ArrayList<>(1000000);
ArrayList 擴容本質是 Arrays.copyOf() 創建新數組並複製。可自行封裝變長數組:
public class DynamicArray<T> {
private T[] data;
private int size = 0;
private static final int DEFAULT_CAPACITY = 10;
@SuppressWarnings("unchecked")
public DynamicArray() {
data = (T[]) new Object[DEFAULT_CAPACITY];
}
public void add(T element) {
ensureCapacity(size + 1);
data[size++] = element;
}
private void ensureCapacity(int minCapacity) {
if (minCapacity > data.length) {
int newCapacity = data.length * 3 / 2; // 1.5 倍擴容
data = Arrays.copyOf(data, newCapacity);
}
}
}
Arrays.copyOf()、clone()、System.arraycopy() 均為淺拷貝。
// 陷阱示例
int[][] matrix = {{1,2}, {3,4}};
int[][] copy = Arrays.copyOf(matrix, matrix.length);
copy[0][0] = 99;
// 原數組也被修改!matrix[0][0] == 99
深拷貝方案:手動遞歸拷貝、序列化反序列化、JSON 轉換、Stream API。
ArrayList 默認初始容量 10,擴容觸發 copyOf 開銷大。
// 差:多次擴容
List<User> users = new ArrayList<>();
for (int i = 0; i < 10000; i++) users.add(getUser());
// 優:一次分配
List<User> users = new ArrayList<>(10000);
同理適用於 HashMap:new HashMap<>(expectedSize),避免重複 rehash。
// 方案一:單次遍歷 O(n) - 推薦
public static int getMaxLinear(int[] data) {
int max = data[0];
for (int i = 1; i < data.length; i++) {
if (data[i] > max) max = data[i];
}
return max;
}
// 方案二:排序後取值 O(n log n) - 不推薦用於單純求最值
public static int getMaxBySort(int[] data) {
Arrays.sort(data);
return data[data.length - 1];
}
除非需要有序數組,否則單次遍歷最優。
int[] nums = {1, 2, 3};
List<int[]> list = Arrays.asList(nums); // 錯誤!List<int[]> size=1
// 正確做法:
List<Integer> list = Arrays.stream(nums).boxed().collect(Collectors.toList());
// 或使用 Guava: Ints.asList(nums)
原因:基本類型數組無法泛型化,被視為單個對象。
List list = Arrays.asList("a", "b", "c");
list.add("d"); // 拋出 UnsupportedOperationException
list.set(0, "x"); // 允許:修改現有元素
原因:返回的是 Arrays.ArrayList(內部類),未實現 add/remove。需轉換:new ArrayList<>(Arrays.asList(...))。
| 集合類型 | 推薦遍歷 | 原因 |
|---|---|---|
| ArrayList | for 循環下標訪問 | 實現 RandomAccess,隨機訪問 O(1) |
| LinkedList | foreach / 迭代器 | 雙向鏈表,下標訪問 O(n),順序訪問 O(1) |
// ArrayList:下標遍歷最快
for (int i = 0, size = list.size(); i < size; i++) {
process(list.get(i));
}
// LinkedList:迭代器遍歷
for (String s : linkedList) {
process(s);
}
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 隨機訪問 | O(1) ✓ | O(n) |
| 頭/尾插入刪除 | O(n) | O(1) ✓ |
| 中間插入刪除 | O(n) | O(n) 但無需移動數組 |
| 內存佔用 | 較小 | 較大(節點對象開銷) |
經驗法則:默認用 ArrayList;作為隊列/棧、頻繁頭尾操作用 LinkedList 或 ArrayDeque。
集合 equals() 已重寫:只比較元素內容與順序,不比較具體實現類。
List<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2));
List<Integer> linkedList = new LinkedList<>(Arrays.asList(1, 2));
arrayList.equals(linkedList); // true
這讓面向接口編程更安心。