【数据结构】List 详解
目录
1. List 的核心概念
核心特性:
2. List 的抽象数据类型(ADT)
基本操作
3. List 的主要实现方式
3.1 顺序表(基于数组)
3.2 链表(基于节点)
4. Java 中的 List 接口及实现类
4.1 List 接口的继承关系
4.2 主要实现类对比
5. 具体实现详解
5.1 ArrayList - 基于动态数组的实现
5.2 LinkedList - 基于链表的实现
6. 时间复杂度分析
7. 使用场景和选择建议
选择 ArrayList 当:
选择 LinkedList 当:
示例:性能对比场景
8. 常见算法和应用
8.1 列表反转
8.2 列表排序
1. List 的核心概念
List(列表/线性表) 是一种线性数据结构,它表示一个有序的、可重复的元素序列。
核心特性:
-
有序性:元素按照插入的顺序存储,每个元素都有确定的位置(索引)
-
可重复性:允许包含重复的元素
-
动态大小:大多数实现支持动态扩容
-
随机访问:可以通过整数索引直接访问任何位置的元素
2. List 的抽象数据类型(ADT)
在讨论具体实现之前,先了解 List 应该支持哪些基本操作:
基本操作
操作 | 方法签名 | 描述 |
---|---|---|
添加 |
| 在末尾添加元素 |
插入 |
| 在指定位置插入元素 |
获取 |
| 获取指定位置的元素 |
设置 |
| 修改指定位置的元素 |
删除 |
| 删除指定位置的元素 |
删除元素 |
| 删除第一次出现的指定元素 |
大小 |
| 返回元素个数 |
判空 |
| 判断是否为空 |
包含 |
| 判断是否包含某元素 |
查找 |
| 返回元素第一次出现的索引 |
清空 |
| 清空所有元素 |
3. List 的主要实现方式
List 有两种经典的实现方式,对应不同的底层数据结构:
3.1 顺序表(基于数组)
-
底层结构:动态数组
-
代表实现:
ArrayList
、Vector
-
特点:
-
内存连续分配
-
支持快速随机访问(O(1))
-
尾部操作高效,中间插入/删除需要移动元素
-
3.2 链表(基于节点)
-
底层结构:节点通过指针连接
-
代表实现:
LinkedList
-
特点:
-
内存非连续分配
-
插入删除高效(O(1),已知位置时)
-
随机访问需要遍历(O(n))
-
4. Java 中的 List 接口及实现类
4.1 List 接口的继承关系
// List 是一个泛型接口
public interface List<E> extends Collection<E> {// 定义了一系列列表操作的方法
}
4.2 主要实现类对比
特性 | ArrayList | LinkedList | Vector |
---|---|---|---|
底层结构 | 动态数组 | 双向链表 | 动态数组 |
线程安全 | 否 | 否 | 是(同步) |
随机访问 | O(1) - 很快 | O(n) - 慢 | O(1) - 很快 |
头部插入 | O(n) - 需要移动元素 | O(1) - 很快 | O(n) - 需要移动元素 |
尾部插入 | O(1) 平均 - 快 | O(1) - 很快 | O(1) 平均 - 快 |
中间插入 | O(n) - 需要移动元素 | O(1)(已知位置时) | O(n) - 需要移动元素 |
内存开销 | 较小(只存数据) | 较大(需要存储指针) | 较小(只存数据) |
5. 具体实现详解
5.1 ArrayList - 基于动态数组的实现
// ArrayList 的基本使用
List<String> arrayList = new ArrayList<>();// 添加元素
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add(1, "Orange"); // 在索引1处插入// 访问元素
String fruit = arrayList.get(0); // "Apple"// 遍历方式1:for循环(随机访问特性体现)
for (int i = 0; i < arrayList.size(); i++) {System.out.println(arrayList.get(i));
}// 遍历方式2:增强for循环
for (String item : arrayList) {System.out.println(item);
}// 遍历方式3:迭代器
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {System.out.println(iterator.next());
}
ArrayList 的扩容机制:
// 当容量不足时,ArrayList会自动扩容
List<Integer> list = new ArrayList<>(5); // 初始容量5
for (int i = 0; i < 10; i++) {list.add(i); // 当添加第6个元素时,会触发扩容
}// 扩容过程(简化):
// 1. 创建新数组(通常是原容量的1.5倍)
// 2. 将旧数组元素拷贝到新数组
// 3. 更新内部数组引用
5.2 LinkedList - 基于链表的实现
// LinkedList 的基本使用
List<String> linkedList = new LinkedList<>();// 添加元素
linkedList.add("First");
linkedList.add("Second");
linkedList.addFirst("Head"); // 链表特有的方法
linkedList.addLast("Tail"); // 链表特有的方法// LinkedList 还实现了 Deque 接口,可以用作栈或队列
Deque<String> deque = (Deque<String>) linkedList;
deque.offerFirst("New Head");
deque.offerLast("New Tail");
链表节点结构:
// 双向链表节点的简化表示
class Node<E> {E item; // 存储的数据Node<E> next; // 指向下一个节点Node<E> prev; // 指向上一个节点
}
6. 时间复杂度分析
操作 | ArrayList | LinkedList | 说明 |
---|---|---|---|
get(int index) | O(1) | O(n) | ArrayList通过索引直接计算地址 |
add(E element) | O(1) 平均 | O(1) | 尾部添加 |
add(int index, E element) | O(n) | O(1)(已知位置) | 中间插入 |
remove(int index) | O(n) | O(1)(已知位置) | 中间删除 |
set(int index, E element) | O(1) | O(n) | 修改指定位置 |
contains(Object o) | O(n) | O(n) | 需要遍历查找 |
indexOf(Object o) | O(n) | O(n) | 需要遍历查找 |
7. 使用场景和选择建议
选择 ArrayList 当:
-
频繁随机访问元素(按索引读取)
-
主要在尾部进行添加/删除操作
-
内存相对紧张,希望减少开销
-
不需要线程安全(或可以自行处理同步)
选择 LinkedList 当:
-
频繁在任意位置插入/删除元素
-
需要实现栈、队列、双端队列
-
内存充足,可以接受额外指针开销
-
列表规模很大,中间操作频繁
示例:性能对比场景
// 场景1:频繁随机访问 - 适合ArrayList
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {int value = list.get(i); // ArrayList: O(1), LinkedList: O(n)
}// 场景2:频繁在头部插入 - 适合LinkedList
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 100000; i++) {list.add(0, i); // ArrayList: O(n), LinkedList: O(1)
}
8. 常见算法和应用
8.1 列表反转
// 方法1:使用Collections工具类
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Collections.reverse(list);// 方法2:手动实现
public static <T> void reverseList(List<T> list) {int left = 0, right = list.size() - 1;while (left < right) {T temp = list.get(left);list.set(left, list.get(right));list.set(right, temp);left++;right--;}
}
8.2 列表排序
List<Integer> numbers = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));// 自然排序
Collections.sort(numbers);// 自定义排序
Collections.sort(numbers, (a, b) -> b - a); // 降序// 使用Stream API排序
List<Integer> sorted = numbers.stream().sorted().collect(Collectors.toList());
List 是最基础、最常用的数据结构之一,理解其特性和实现方式对于编程至关重要:
-
ArrayList:基于动态数组,查询快,增删慢,适合查询多的场景
-
LinkedList:基于双向链表,增删快,查询慢,适合频繁插入删除的场景
-
Vector:线程安全的ArrayList,但性能较差,一般不推荐使用
选择原则:
-
80%的情况下,ArrayList 是默认选择
-
只有在需要频繁在列表中间操作时,才考虑 LinkedList
-
在多线程环境下,考虑使用
Collections.synchronizedList()
或CopyOnWriteArrayList