目录
[1. List 的核心概念](#1. List 的核心概念)
[2. List 的抽象数据类型(ADT)](#2. List 的抽象数据类型(ADT))
[3. List 的主要实现方式](#3. List 的主要实现方式)
[3.1 顺序表(基于数组)](#3.1 顺序表(基于数组))
[3.2 链表(基于节点)](#3.2 链表(基于节点))
[4. Java 中的 List 接口及实现类](#4. Java 中的 List 接口及实现类)
[4.1 List 接口的继承关系](#4.1 List 接口的继承关系)
[4.2 主要实现类对比](#4.2 主要实现类对比)
[5. 具体实现详解](#5. 具体实现详解)
[5.1 ArrayList - 基于动态数组的实现](#5.1 ArrayList - 基于动态数组的实现)
[5.2 LinkedList - 基于链表的实现](#5.2 LinkedList - 基于链表的实现)
[6. 时间复杂度分析](#6. 时间复杂度分析)
[7. 使用场景和选择建议](#7. 使用场景和选择建议)
[选择 ArrayList 当:](#选择 ArrayList 当:)
[选择 LinkedList 当:](#选择 LinkedList 当:)
[8. 常见算法和应用](#8. 常见算法和应用)
[8.1 列表反转](#8.1 列表反转)
[8.2 列表排序](#8.2 列表排序)
1. List 的核心概念
List(列表/线性表) 是一种线性数据结构,它表示一个有序的、可重复的元素序列。
核心特性:
-
有序性:元素按照插入的顺序存储,每个元素都有确定的位置(索引)
-
可重复性:允许包含重复的元素
-
动态大小:大多数实现支持动态扩容
-
随机访问:可以通过整数索引直接访问任何位置的元素
2. List 的抽象数据类型(ADT)
在讨论具体实现之前,先了解 List 应该支持哪些基本操作:
基本操作
操作 | 方法签名 | 描述 |
---|---|---|
添加 | add(E element) |
在末尾添加元素 |
插入 | add(int index, E element) |
在指定位置插入元素 |
获取 | get(int index) |
获取指定位置的元素 |
设置 | set(int index, E element) |
修改指定位置的元素 |
删除 | remove(int index) |
删除指定位置的元素 |
删除元素 | remove(Object o) |
删除第一次出现的指定元素 |
大小 | size() |
返回元素个数 |
判空 | isEmpty() |
判断是否为空 |
包含 | contains(Object o) |
判断是否包含某元素 |
查找 | indexOf(Object o) |
返回元素第一次出现的索引 |
清空 | clear() |
清空所有元素 |
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