👉 用 ArrayList
存數據,結果插入時卡住了?
👉 想刪除某個元素,卻發現索引錯亂了?
👉 不知道該用 ArrayList
還是 LinkedList
,選錯了導致性能瓶頸?
一、List
?是什么?—— 有序可重復的“列表”
🎯 核心特點
- 有序(Ordered):元素按插入順序排列
- 允許重復:可以存儲多個相同的值
- 支持索引訪問:可以通過?
get(index)
?快速定位元素
? 典型場景:
- 用戶列表展示
- 購物車商品管理
- 日志記錄
🖼? 圖示:List
?的邏輯結構
+--------+ +--------+ +--------+
| index0 | -> | index1 | -> | index2 | -> ...
+--------+ +--------+ +--------+
| valueA | | valueB | | valueC |
+--------+ +--------+ +--------+
? 核心操作:
- 添加元素:
add(E e)
- 獲取元素:
get(int index)
- 刪除元素:
remove(int index)
二、List
?的核心實現類
1.?ArrayList
?—— 基于“動態數組”的實現
🎯 核心特性
- 底層是數組:
Object[] elementData
- 支持快速隨機訪問:通過索引直接定位,時間復雜度?
O(1)
- 動態擴容:當容量不夠時,自動擴展(默認擴容 1.5 倍)
- 非線程安全
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
String first = list.get(0); // 快速獲取第一個元素
🔍 動態擴容機制
private void grow(int minCapacity) {int oldCapacity = elementData.length;// 新容量 = 舊容量 + 舊容量/2 (即 1.5 倍)int newCapacity = oldCapacity + (oldCapacity >> 1);// 創建新數組,復制數據elementData = Arrays.copyOf(elementData, newCapacity);
}
內存變化:
擴容前:[Apple][Banana] (容量=2, size=2)
擴容后:[Apple][Banana][ ] (容量=3, size=2)
? 建議:初始化時指定合理容量,避免頻繁擴容影響性能。
? 經典誤區:中間插入/刪除慢
// 錯誤:頻繁在中間插入/刪除,導致后面元素移動,性能 O(n)
list.add(1, "Orange"); // 插入到索引 1
list.remove(1); // 刪除索引 1
? 結論:
ArrayList
適合查詢多、增刪少的場景。
2.?LinkedList
?—— 基于“雙向鏈表”的實現
🎯 核心特性
- 底層是雙向鏈表:每個節點包含前后指針
- 頭尾增刪快:時間復雜度?
O(1)
- 支持棧/隊列操作:實現了?
Deque
?接口 - 非線程安全
LinkedList<String> list = new LinkedList<>();
list.addFirst("Apple"); // 頭插
list.addLast("Banana"); // 尾插
String first = list.getFirst(); // 快速獲取第一個元素
🖼? 圖示:雙向鏈表的內存布局
地址 2000: +--------+--------+--------+|prev=null| data=A |next=3000|+--------+--------+--------+地址 3000: +--------+--------+--------+|prev=2000| data=B |next=null|+--------+--------+--------+
邏輯結構:
head tail| |v v
+--------+ +--------+
|null|A|<--->|A|B|null|
+--------+ +--------+2000 3000
? 適用場景:頻繁在頭部或尾部增刪元素,如消息隊列、棧操作。
? 經典誤區:隨機訪問慢
// 錯誤:頻繁隨機訪問,導致遍歷整個鏈表,性能 O(n)
String third = list.get(2); // 需要遍歷兩次才能找到第三個元素
? 結論:
LinkedList
適合頭尾操作多、隨機訪問少的場景。
三、List
?的常用方法詳解
1. 添加元素
// 在末尾添加
list.add("Apple");// 在指定位置插入
list.add(1, "Banana"); // 插入到索引 1
2. 獲取元素
// 通過索引獲取
String first = list.get(0);// 獲取第一個/最后一個元素
String first = list.get(0);
String last = list.get(list.size() - 1);
3. 刪除元素
// 刪除指定位置的元素
list.remove(1); // 刪除索引 1 的元素// 刪除指定對象(第一次出現)
list.remove("Apple");
4. 替換元素
// 替換指定位置的元素
list.set(1, "Grape"); // 將索引 1 的元素替換為 Grape
5. 查找元素
// 判斷是否包含某個元素
boolean contains = list.contains("Apple");// 查找元素的位置
int index = list.indexOf("Apple"); // 返回第一個 Apple 的索引
int lastIndex = list.lastIndexOf("Apple"); // 返回最后一個 Apple 的索引
6. 子列表操作
// 獲取子列表
List<String> subList = list.subList(1, 3); // 包含索引 1 和 2 的元素
四、List
?的線程安全版本
1.?Collections.synchronizedList()
?—— 簡單粗暴的同步
List<String> syncList = Collections.synchronizedList(new ArrayList<>());synchronized (syncList) {syncList.add("Apple");
}
? 缺點:
- 整個?
List
?對象加鎖,粒度過大,性能差。- 不支持并發讀寫,可能導致阻塞。
2.?CopyOnWriteArrayList
?—— 寫時復制的線程安全?List
🎯 核心特性
- 寫時復制:每次修改(
add
,?remove
)都會創建一個新數組副本。 - 適用于讀多寫少的場景:讀操作無鎖,寫操作加鎖但不影響讀。
List<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("Apple");
cowList.add("Banana");
? 適用場景:
- 并發讀多、寫少的場景,如日志記錄、事件監聽器等。
五、高頻問題 & 高分回答
Q1:?ArrayList
?和?LinkedList
?如何選擇?
答:
ArrayList
:基于數組,查詢快(O(1)
),增刪慢(O(n)
);
適合隨機訪問多、增刪少的場景。LinkedList
:基于鏈表,增刪快(O(1)
?頭尾),查詢慢(O(n)
);
適合頻繁頭尾增刪的場景,或實現棧/隊列。
我們項目中用戶列表用?ArrayList
,消息隊列用?LinkedList
。
Q2:?ArrayList
?的擴容機制是怎樣的?
答:
- 默認容量 10;
- 擴容時,新容量 = 原容量 × 1.5;
- 使用?
Arrays.copyOf()
?復制數據;- 擴容是耗時操作,建議初始化時指定合理容量。
Q3:?LinkedList
?的優勢和劣勢是什么?
答:
- 優勢:頭尾增刪快(
O(1)
),支持棧/隊列操作;- 劣勢:隨機訪問慢(
O(n)
),內存開銷稍大(每個節點有前后指針)。
適合頻繁頭尾操作的場景,如消息隊列、棧操作。
Q4:?CopyOnWriteArrayList
?的工作原理?
答:
- 寫時復制:每次修改(
add
,?remove
)都會創建一個新數組副本;- 讀操作無鎖,寫操作加鎖但不影響讀;
- 適用于讀多寫少的場景,如日志記錄、事件監聽器等。
六、總結:一張表搞懂?List
?的選型
場景 | 推薦實現 | 關鍵點 |
---|---|---|
隨機訪問多、增刪少 | ArrayList | 查詢快,擴容注意 |
頭尾增刪多、隨機訪問少 | LinkedList | 頭尾操作極快,隨機訪問慢 |
并發讀多寫少 | CopyOnWriteArrayList | 寫時復制,讀無鎖 |
🔚 最后一句話
List
是 Java 集合框架中最基礎、最常用的接口之一。
它不僅僅是一個“容器”,更是我們日常開發中處理有序數據的核心工具。
只有當你真正理解了ArrayList
的擴容機制、LinkedList
的雙向鏈表結構,
以及CopyOnWriteArrayList
的寫時復制原理,
你才能寫出高效、健壯、專業的 Java 代碼!
希望這篇能幫你徹底搞懂 List
接口及其常見實現!