在 Java 集合框架中,ArrayList 和 LinkedList 都是 List 接口的实现类,用于存储有序可重复的元素,但它们的底层数据结构和特性有显著差异。以下从多个维度详细说明,并结合代码示例解析:

1. 底层数据结构

  • ArrayList:基于动态数组实现(连续内存空间),元素在内存中连续存储,通过索引(下标)快速访问。

  • LinkedList:基于双向链表实现(非连续内存空间),每个元素(节点)包含数据域和两个指针域(prev 指向前驱节点,next 指向后继节点),元素在内存中分散存储。

2. 核心区别对比

对比维度ArrayListLinkedList
底层结构动态数组(连续内存)双向链表(非连续内存)
访问效率高(通过索引直接访问,时间复杂度 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 增删)的频率和位置决定,避免因误用导致性能问题。