👉 用 ArrayList 存数据,结果插入时卡住了?
👉 想删除某个元素,却发现索引错乱了?
👉 不知道该用 ArrayList 还是 LinkedList,选错了导致性能瓶颈?
List 是什么?—— 有序可重复的“列表”get(index) 快速定位元素✅ 典型场景:
List 的逻辑结构+--------+ +--------+ +--------+
| index0 | -> | index1 | -> | index2 | -> ...
+--------+ +--------+ +--------+
| valueA | | valueB | | valueC |
+--------+ +--------+ +--------+✅ 核心操作:
add(E e)get(int index)remove(int index)List 的核心实现类ArrayList —— 基于“动态数组”的实现Object[] elementDataO(1)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适合查询多、增删少的场景。
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 的常用方法详解// 在末尾添加
list.add("Apple");
// 在指定位置插入
list.add(1, "Banana"); // 插入到索引 1// 通过索引获取
String first = list.get(0);
// 获取第一个/最后一个元素
String first = list.get(0);
String last = list.get(list.size() - 1);// 删除指定位置的元素
list.remove(1); // 删除索引 1 的元素
// 删除指定对象(第一次出现)
list.remove("Apple");// 替换指定位置的元素
list.set(1, "Grape"); // 将索引 1 的元素替换为 Grape// 判断是否包含某个元素
boolean contains = list.contains("Apple");
// 查找元素的位置
int index = list.indexOf("Apple"); // 返回第一个 Apple 的索引
int lastIndex = list.lastIndexOf("Apple"); // 返回最后一个 Apple 的索引// 获取子列表
List<String> subList = list.subList(1, 3); // 包含索引 1 和 2 的元素List 的线程安全版本Collections.synchronizedList() —— 简单粗暴的同步List<String> syncList = Collections.synchronizedList(new ArrayList<>());
synchronized (syncList) {
syncList.add("Apple");
}❌ 缺点:
List 对象加锁,粒度过大,性能差。CopyOnWriteArrayList —— 写时复制的线程安全 Listadd, remove)都会创建一个新数组副本。List<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("Apple");
cowList.add("Banana");✅ 适用场景:
ArrayList 和 LinkedList 如何选择?答:
ArrayList:基于数组,查询快(O(1)),增删慢(O(n));
适合随机访问多、增删少的场景。LinkedList:基于链表,增删快(O(1) 头尾),查询慢(O(n));
适合频繁头尾增删的场景,或实现栈/队列。
我们项目中用户列表用 ArrayList,消息队列用 LinkedList。ArrayList 的扩容机制是怎样的?答:
Arrays.copyOf() 复制数据;LinkedList 的优势和劣势是什么?答:
O(1)),支持栈/队列操作;O(n)),内存开销稍大(每个节点有前后指针)。
适合频繁头尾操作的场景,如消息队列、栈操作。CopyOnWriteArrayList 的工作原理?答:
add, remove)都会创建一个新数组副本;List 的选型场景 | 推荐实现 | 关键点 |
|---|---|---|
随机访问多、增删少 | ArrayList | 查询快,扩容注意 |
头尾增删多、随机访问少 | LinkedList | 头尾操作极快,随机访问慢 |
并发读多写少 | CopyOnWriteArrayList | 写时复制,读无锁 |
List是 Java 集合框架中最基础、最常用的接口之一。 它不仅仅是一个“容器”,更是我们日常开发中处理有序数据的核心工具。 只有当你真正理解了ArrayList的扩容机制、LinkedList的双向链表结构, 以及CopyOnWriteArrayList的写时复制原理, 你才能写出高效、健壮、专业的 Java 代码!
希望这篇能帮你彻底搞懂 List 接口及其常见实现!