一、List接口:有序集合的完整契约
1.1 List接口的特性
List是Java集合框架中最常用的接口之一,它具有以下核心特征:
-
有序性:元素按照插入顺序保存,可以通过索引访问
-
可重复性:允许存储相同的元素
-
支持索引操作:提供了基于位置(索引)的各种方法
-
允许null元素:大多数实现允许存储null值
1.2 List接口的完整方法体系
java
import java.util.*;
public class ListInterfaceDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 基础Collection方法
list.add("Java");
list.add("Python");
list.add("JavaScript");
// List特有方法
// 1. 按索引添加
list.add(1, "C++");
System.out.println("插入后: " + list);
// 2. 按索引获取
String element = list.get(2);
System.out.println("索引2的元素: " + element);
// 3. 按索引修改
list.set(0, "Java SE");
System.out.println("修改后: " + list);
// 4. 按索引删除
list.remove(3);
System.out.println("删除后: " + list);
// 5. 查找元素位置
int index = list.indexOf("Python");
System.out.println("Python的索引: " + index);
// 6. 查找最后出现位置
list.add("Java SE");
int lastIndex = list.lastIndexOf("Java SE");
System.out.println("Java SE最后出现位置: " + lastIndex);
// 7. 子列表操作
List<String> subList = list.subList(1, 3);
System.out.println("子列表(1,3): " + subList);
// 8. 列表迭代器(双向遍历)
ListIterator<String> listIterator = list.listIterator();
System.out.println("\n=== 双向遍历 ===");
while (listIterator.hasNext()) {
System.out.print(listIterator.next() + " ");
}
System.out.println("\n反向遍历:");
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() + " ");
}
}
}
二、ArrayList:基于动态数组的实现
2.1 ArrayList的内部结构
java
// ArrayList的简化内部结构示意
public class ArrayList<E> {
// 核心:使用Object数组存储元素
private Object[] elementData;
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 实际元素数量
private int size;
// 构造函数
public ArrayList() {
this.elementData = new Object[DEFAULT_CAPACITY];
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
}
}
}
2.2 ArrayList的核心操作源码分析
java
import java.util.ArrayList;
public class ArrayListDeepDive {
public static void main(String[] args) {
// 创建ArrayList的三种方式
ArrayList<String> list1 = new ArrayList<>(); // 默认容量10
ArrayList<String> list2 = new ArrayList<>(20); // 指定初始容量
ArrayList<String> list3 = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 容量动态增长机制
System.out.println("=== ArrayList扩容机制 ===");
ArrayList<Integer> numbers = new ArrayList<>(3);
for (int i = 1; i <= 10; i++) {
numbers.add(i);
System.out.printf("添加第%d个元素,当前容量估算: %d%n",
i, getCapacity(numbers));
}
// 性能测试:随机访问 vs 插入删除
performanceTest();
}
// 反射获取ArrayList容量(仅供演示)
private static int getCapacity(ArrayList<?> list) {
try {
java.lang.reflect.Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
Object[] elementData = (Object[]) field.get(list);
return elementData.length;
} catch (Exception e) {
return -1;
}
}
private static void performanceTest() {
System.out.println("\n=== 性能测试 ===");
int size = 100000;
// 1. 随机访问测试
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) {
arrayList.add(i);
}
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
arrayList.get((int) (Math.random() * size));
}
long end = System.nanoTime();
System.out.printf("ArrayList随机访问1000次耗时: %d ns%n", end - start);
// 2. 头部插入测试
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
arrayList.add(0, i); // 在头部插入
}
end = System.nanoTime();
System.out.printf("ArrayList头部插入1000次耗时: %d ns%n", end - start);
}
}
2.3 ArrayList的迭代与快速失败机制
java
import java.util.*;
public class ArrayListIteration {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(
Arrays.asList("Java", "Python", "C++", "JavaScript", "Go")
);
// 1. 普通for循环(适合随机访问)
System.out.println("=== 普通for循环 ===");
for (int i = 0; i < list.size(); i++) {
System.out.println("索引" + i + ": " + list.get(i));
}
// 2. 增强for循环(语法糖,底层使用迭代器)
System.out.println("\n=== 增强for循环 ===");
for (String lang : list) {
System.out.println(lang);
}
// 3. 迭代器遍历
System.out.println("\n=== 迭代器遍历 ===");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String lang = iterator.next();
System.out.println(lang);
}
// 4. 列表迭代器(功能更强大)
System.out.println("\n=== 列表迭代器 ===");
ListIterator<String> listIterator = list.listIterator(2); // 从索引2开始
while (listIterator.hasNext()) {
int index = listIterator.nextIndex();
String lang = listIterator.next();
System.out.println("索引" + index + ": " + lang);
}
// 5. forEach方法(Java 8+)
System.out.println("\n=== forEach方法 ===");
list.forEach(System.out::println);
// 6. 并发修改异常演示
System.out.println("\n=== 快速失败机制演示 ===");
try {
for (String lang : list) {
if ("C++".equals(lang)) {
list.remove(lang); // 抛出ConcurrentModificationException
}
}
} catch (ConcurrentModificationException e) {
System.out.println("捕获到并发修改异常: " + e.getMessage());
}
// 正确的方式:使用迭代器的remove方法
System.out.println("\n=== 安全删除 ===");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String lang = it.next();
if ("C++".equals(lang)) {
it.remove(); // 安全删除
}
}
System.out.println("删除后: " + list);
}
}
三、LinkedList:基于双向链表的实现
3.1 LinkedList的内部结构
java
// LinkedList节点结构的简化示意
private static class Node<E> {
E item; // 存储的元素
Node<E> next; // 指向下一个节点
Node<E> prev; // 指向前一个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
3.2 LinkedList的核心操作
java
import java.util.*;
public class LinkedListDemo {
public static void main(String[] args) {
// LinkedList实现了List和Deque接口
LinkedList<String> linkedList = new LinkedList<>();
// 1. 作为List的基本操作
linkedList.add("Java");
linkedList.add("Python");
linkedList.addFirst("C"); // 添加到头部
linkedList.addLast("Go"); // 添加到尾部
System.out.println("初始链表: " + linkedList);
System.out.println("第一个元素: " + linkedList.getFirst());
System.out.println("最后一个元素: " + linkedList.getLast());
// 2. 作为队列的操作
System.out.println("\n=== 队列操作 ===");
Queue<String> queue = linkedList;
queue.offer("新任务"); // 入队(添加到尾部)
System.out.println("出队: " + queue.poll()); // 出队(从头部移除)
// 3. 作为栈的操作
System.out.println("\n=== 栈操作 ===");
Deque<String> stack = linkedList;
stack.push("栈顶元素"); // 压栈
System.out.println("栈顶: " + stack.peek());
System.out.println("弹栈: " + stack.pop());
// 4. 性能测试:插入删除
performanceTest();
}
private static void performanceTest() {
System.out.println("\n=== LinkedList性能测试 ===");
int size = 100000;
LinkedList<Integer> linkedList = new LinkedList<>();
// 头部插入性能
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
linkedList.addFirst(i); // 头部插入
}
long end = System.nanoTime();
System.out.printf("LinkedList头部插入1000次耗时: %d ns%n", end - start);
// 随机访问性能
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
linkedList.get((int) (Math.random() * linkedList.size()));
}
end = System.nanoTime();
System.out.printf("LinkedList随机访问1000次耗时: %d ns%n", end - start);
}
}
四、ArrayList vs LinkedList:全方位对比
数据结构对比
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 内存占用 | 连续内存,预留空间 | 分散内存,每个元素额外存储指针 |
| 默认容量 | 10 | 无(动态增长) |
| 扩容机制 | 1.5倍增长 | 无需扩容,动态创建节点 |
五、选择指南:何时使用ArrayList,何时使用LinkedList
5.1 使用ArrayList的场景
场景1:需要频繁随机访问
场景2:存储大量数据,内存敏感
场景3:尾部操作为主
5.2 使用LinkedList的场景
场景1:需要频繁在头部/中部插入删除
场景2:需要实现栈、队列、双端队列
场景3:中间位置操作频繁
六、总结与建议
6.1 核心要点回顾
-
ArrayList优势:
-
随机访问速度快(O(1))
-
内存占用少(连续存储)
-
尾部操作效率高
-
-
LinkedList优势:
-
头部/中间插入删除快(O(1)找到位置后)
-
可以实现多种数据结构(栈、队列、双端队列)
-
无扩容开销
-
-
通用原则:
-
大多数情况下,ArrayList是默认选择
-
只有在特定场景下才选择LinkedList
-
根据具体操作类型选择合适的数据结构
-
6.2 决策流程图
text
开始选择List实现
│
├─ 是否需要频繁随机访问?
│ ├─ 是 → 选择ArrayList
│ └─ 否 →
│ ├─ 是否需要频繁在头部/中间插入删除?
│ │ ├─ 是 → 选择LinkedList
│ │ └─ 否 →
│ │ ├─ 是否需要实现栈/队列/双端队列?
│ │ │ ├─ 是 → 选择LinkedList
│ │ │ └─ 否 → 选择ArrayList(默认)
│ │ └─ 内存是否非常紧张?
│ │ ├─ 是 → 测试两种实现的实际内存占用
│ │ └─ 否 → 选择ArrayList
│ └─ 数据量是否特别大?
│ ├─ 是 → 考虑分页或数据库
│ └─ 否 → 选择ArrayList
└─ 是否需要线程安全?
├─ 是 → 考虑CopyOnWriteArrayList或Collections.synchronizedList
└─ 否 → 继续上述判断
下篇预告:《Java集合(三):Set接口详解与HashSet、TreeSet、LinkedHashSet比较》
通过本篇学习,你应该已经掌握了List接口的核心知识以及ArrayList和LinkedList的详细对比。在实际开发中,合理选择数据结构是写出高效代码的关键。记住:没有最好的数据结构,只有最适合的场景!