Skip to content

ArrayList 和 LinkedList 的区别和应用

约 882 字大约 3 分钟

Java基础字节

2025-03-20

⭐ 题目日期:

字节 - 2024/12/10

📝 题解:

在Java中,ArrayListLinkedList都是List接口的实现类,但底层实现和性能特性有显著差异。以下是它们的核心区别及适用场景的详细分析,符合面试回答的深度和结构要求:


一、底层数据结构

  1. ArrayList
    基于动态数组:内部使用数组存储元素,通过索引直接访问数据。 • 自动扩容:当容量不足时,默认扩容50%(如从10扩容到15),通过Arrays.copyOf复制数据,时间复杂度为O(n)

  2. LinkedList
    基于双向链表:每个节点(Node)包含前驱指针、数据、后继指针,内存空间不连续。 • 无需扩容:每次插入只需创建新节点并调整指针,时间复杂度O(1)


二、关键性能对比

1. 随机访问效率

ArrayList
通过索引直接定位数组位置,时间复杂度为O(1)
示例list.get(1000) 效率极高。

LinkedList
需要从头或尾遍历链表,直到找到目标索引,时间复杂度为O(n)
示例list.get(1000) 需要遍历1000次指针。

2. 插入/删除效率

尾部操作
ArrayList:若无需扩容,尾部插入为O(1)(均摊时间复杂度);扩容时需复制数组,最坏O(n)
LinkedList:尾部插入直接修改尾节点指针,O(1)

头部/中间操作
ArrayList:需移动后续元素,时间复杂度O(n)
示例:在索引0插入元素,需要将整个数组后移。
LinkedList
◦ 若已持有目标位置的引用(如通过迭代器),插入/删除为O(1)
◦ 若需先查找位置(如通过索引),查找+操作为O(n)

3. 内存占用

ArrayList
• 数组空间连续,内存利用率高。
• 可能浪费空间(扩容预留容量)。

LinkedList
• 每个节点需额外存储前后指针(每个节点多消耗约12~16字节内存)。
• 内存碎片化,可能影响缓存局部性(Cache Locality)。


三、适用场景

1. 优先选择 ArrayList

高频随机访问:如按索引读取或遍历元素。
示例:数据库查询结果的列表展示。 • 尾部插入为主:避免频繁扩容时,可初始化较大容量。
内存敏感场景:数据量大时,内存占用更低。

2. 优先选择 LinkedList

频繁在头部/中间插入或删除:如实现栈(Stack)、队列(Queue)或双向队列(Deque)。
示例LinkedList实现了Deque接口,适合需要addFirst()/addLast()的场景。 • 无需随机访问:数据通过迭代器顺序访问,避免低效索引查询。


四、面试扩展点

1. 线程安全问题

• 两者均非线程安全,多线程环境下需通过以下方式保证安全:
• 使用Collections.synchronizedList()包装。
• 改用CopyOnWriteArrayList(写时复制,适合读多写少场景)。

2. 遍历性能

ArrayList
优先用for循环(索引访问)或Iterator,性能接近。 • LinkedList
必须用IteratorforEach,避免用for循环(触发O(n²)遍历)。

3. 设计思想

空间与时间的权衡
ArrayList以空间换时间(快速访问),LinkedList以时间换空间(动态插入)。


五、代码示例

// ArrayList:随机访问高效
List<Integer> arrayList = new ArrayList<>();
arrayList.add(1); // 尾部插入O(1)(无扩容时)
int value = arrayList.get(0); // O(1)

// LinkedList:头部插入高效
List<Integer> linkedList = new LinkedList<>();
linkedList.addFirst(1); // O(1)
linkedList.removeLast(); // O(1)