在 Java 集合框架中,ArrayList 和 LinkedList 都是 List 接口的实现类,用于存储有序可重复的元素,但它们的底层数据结构和特性有显著差异。以下从多个维度详细说明,并结合代码示例解析:
1. 底层数据结构
ArrayList:基于动态数组实现(连续内存空间),元素在内存中连续存储,通过索引(下标)快速访问。
LinkedList:基于双向链表实现(非连续内存空间),每个元素(节点)包含数据域和两个指针域(
prev指向前驱节点,next指向后继节点),元素在内存中分散存储。
2. 核心区别对比
| 对比维度 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组(连续内存) | 双向链表(非连续内存) |
| 访问效率 | 高(通过索引直接访问,时间复杂度 O(1)) | 低(需从头 / 尾遍历,时间复杂度 O(n)) |
| 增删效率 | 尾部增删快(O(1)),中间增删慢(需移动元素,O(n)) | 头部 / 尾部增删快(O(1)),中间增删需定位节点(O(n)),但修改指针仅 O(1) |
| 内存占用 | 内存连续,浪费较少(动态扩容可能有冗余) | 每个节点需额外存储前后指针,内存开销大 |
| 随机访问 | 支持(通过 get(index) 高效访问) | 不支持(需遍历) |
| 迭代效率 | 迭代速度快(连续内存,缓存友好) | 迭代速度慢(非连续内存,缓存不友好) |
3. 代码示例解析
示例 1:初始化与基本操作
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
// 初始化
List arrayList = new ArrayList<>();
List linkedList = new LinkedList<>();
// 添加元素(尾部)
arrayList.add("A");
arrayList.add("B");
linkedList.add("A");
linkedList.add("B");
// 获取元素(通过索引)
System.out.println(arrayList.get(0)); // 输出:A(O(1) 效率)
System.out.println(linkedList.get(0)); // 输出:A(需从头部遍历,O(n) 效率)
// 遍历元素(增强for循环)
for (String s : arrayList) {
System.out.print(s + " "); // 输出:A B
}
for (String s : linkedList) {
System.out.print(s + " "); // 输出:A B
}
}
}
- 两者都支持
add()、get()等List接口方法,语法上无差异,但底层执行效率不同。 ArrayList.get(index)直接通过数组下标访问,效率极高;LinkedList.get(index)需从头部或尾部开始遍历(若索引小于一半长度从头部遍历,否则从尾部),效率低。
示例 2:增删操作效率对比
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class AddRemoveDemo {
public static void main(String[] args) {
int n = 100000; // 测试数据量
// 测试 ArrayList 中间插入
List arrayList = new ArrayList<>();
long start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
arrayList.add(0, i); // 每次在头部插入(等效于中间插入的极端情况)
}
long end = System.currentTimeMillis();
System.out.println("ArrayList头部插入耗时:" + (end - start) + "ms"); // 耗时较长
// 测试 LinkedList 中间插入
List linkedList = new LinkedList<>();
start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
linkedList.add(0, i); // 头部插入(链表优势场景)
}
end = System.currentTimeMillis();
System.out.println("LinkedList头部插入耗时:" + (end - start) + "ms"); // 耗时极短
}
}
结果分析:
ArrayList在头部插入时,需要将已有元素全部后移(O(n)时间),数据量越大耗时越明显;LinkedList在头部插入时,只需修改头节点的指针(O(1)时间),效率远高于ArrayList。反之,若在尾部插入,两者效率接近(
ArrayList尾部插入通常O(1),LinkedList尾部插入也是O(1))。
示例 3:随机访问性能对比
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class AccessDemo {
public static void main(String[] args) {
int n = 100000;
List arrayList = new ArrayList<>();
List linkedList = new LinkedList<>();
// 初始化数据
for (int i = 0; i < n; i++) {
arrayList.add(i);
linkedList.add(i);
}
// 随机访问测试(通过索引获取元素)
long start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
arrayList.get(i); // O(1) 效率
}
long end = System.currentTimeMillis();
System.out.println("ArrayList随机访问耗时:" + (end - start) + "ms"); // 耗时极短
start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
linkedList.get(i); // O(n) 效率,累计耗时巨大
}
end = System.currentTimeMillis();
System.out.println("LinkedList随机访问耗时:" + (end - start) + "ms"); // 耗时很长
}
}
- 结果分析:
ArrayList的随机访问(get(index))是核心优势,时间复杂度O(1);LinkedList随机访问需逐个遍历节点,累计时间复杂度O(n²),数据量较大时几乎不可用。
示例 4: LinkedList 特有的操作
LinkedList 还实现了 Deque 接口,支持队列 / 栈的操作(首尾元素操作更高效):
LinkedList deque = new LinkedList<>();
deque.addFirst("头部元素"); // 头部添加(等效于 add(0, "xxx"))
deque.addLast("尾部元素"); // 尾部添加(等效于 add("xxx"))
System.out.println(deque.getFirst()); // 获取头部元素
System.out.println(deque.getLast()); // 获取尾部元素
deque.removeFirst(); // 删除头部元素
deque.removeLast(); // 删除尾部元素
- 这些操作在
LinkedList中都是O(1)时间复杂度,而ArrayList需通过add(0, ...)或remove(0)实现,效率低。
4. 适用场景
使用 ArrayList:
- 需要频繁随机访问元素(如通过索引获取数据)。
- 增删操作主要在尾部(如日志记录、动态数组)。
- 对内存占用敏感(连续存储,开销小)。
使用 LinkedList:
- 需要频繁在头部 / 中间进行增删操作(如实现队列、栈、链表结构)。
- 无需随机访问,仅需顺序遍历(如迭代处理所有元素)。
总结
ArrayList 基于动态数组,适合随机访问和尾部增删;LinkedList 基于双向链表,适合头部 / 中间增删和队列 / 栈场景。选择时需根据核心操作(访问 vs 增删)的频率和位置决定,避免因误用导致性能问题。
浙公网安备 33010602011771号