摘要

ArrayListLinkedList 是 Java 面试里非常高频的基础题。很多人只会背“ArrayList 查询快,LinkedList 增删快”,但这个说法其实不够严谨。本文用面试视角,简单梳理它们的底层结构、性能区别、使用场景和常见追问。


前言

上一篇文章梳理了 Java 集合体系,讲了 ListSetMap 怎么分类。

这一篇继续讲 List 下面最常问的两个实现类:

  • ArrayList
  • LinkedList

这个题不难,但面试时很多人容易答得太死:

ArrayList 查询快,LinkedList 增删快。

这句话不是完全错,但不够准确。

这篇就用比较短的方式,把这个问题讲清楚。


一、面试官一般怎么问?

常见问法有这些:

  • ArrayListLinkedList 有什么区别?
  • 它们底层数据结构是什么?
  • 哪个查询快?哪个增删快?
  • 为什么实际开发中 ArrayList 用得更多?
  • LinkedList 真的适合频繁增删吗?
  • ArrayList 扩容机制了解吗?

二、先给结论

一句话先记住:

ArrayList 底层是数组,随机访问快;LinkedList 底层是双向链表,插入删除节点本身方便,但如果要先查找位置,也需要遍历。

更准确一点:

  • ArrayList:适合查询多、尾部追加多的场景
  • LinkedList:适合已经定位到节点后,频繁插入删除的场景
  • 实际开发中,ArrayList 使用频率更高

不要简单死背:

ArrayList 查询快,LinkedList 增删快

这句话面试时最好补一句:

LinkedList 增删快的前提是已经定位到节点。如果还要按下标查找位置,它也需要遍历,不一定比 ArrayList 快。

三、ArrayList 是什么?

ArrayList 底层是数组。

它的特点是:

  • 查询快
  • 支持下标随机访问
  • 尾部追加效率高
  • 中间插入、删除可能需要移动元素
  • 容量不够时会扩容

简单示例:

List<String> list = new ArrayList<>();

list.add("Java");
list.add("Spring");
list.add("MySQL");

System.out.println(list.get(1));

输出:

Spring

因为底层是数组,所以可以通过下标快速访问。

比如:

list.get(1);

它可以直接根据下标定位元素。


四、ArrayList 的优缺点

优点

ArrayList 最大的优点是查询快。

因为数组支持随机访问,按下标取值非常方便。

String value = list.get(0);

这种操作效率很高。

另外,尾部追加元素也比较常见:

list.add("Redis");

只要没有触发扩容,尾部追加效率也不错。


缺点

ArrayList 的缺点主要是中间插入和删除可能比较慢。

比如在中间插入:

list.add(1, "Kafka");

数组后面的元素需要整体往后移动。

删除中间元素也是类似:

list.remove(1);

后面的元素需要往前移动。

所以如果频繁在中间插入、删除,ArrayList 的成本会变高。


五、LinkedList 是什么?

LinkedList 底层是双向链表。

每个节点除了存数据,还会保存前一个节点和后一个节点的引用。

可以简单理解成:

prev <- node -> next

它的特点是:

  • 不支持快速随机访问
  • 查找某个位置需要遍历
  • 插入、删除节点本身比较方便
  • 内存占用相对更高

简单示例:

List<String> list = new LinkedList<>();

list.add("Java");
list.add("Spring");
list.add("MySQL");

System.out.println(list.get(1));

这段代码也能正常运行。

但需要注意:

LinkedList 虽然也有 get(index) 方法,但它不是数组,不能直接通过下标定位。

它需要从头或从尾开始遍历。


六、LinkedList 的优缺点

优点

LinkedList 的优点是节点插入、删除本身比较方便。

如果已经定位到了某个节点,只需要调整前后节点指针。

不像数组那样需要整体移动一批元素。


缺点

LinkedList 的缺点也很明显。

第一,查询慢。

因为它不能像数组一样直接根据下标访问,需要遍历。

第二,内存占用更高。

因为每个节点不仅要存数据,还要存前后指针。

第三,实际开发中很多所谓“增删场景”,也需要先查找位置。

如果你要先按下标找到某个位置,再插入或删除,那么查找这一步就已经有成本了。

所以不要简单认为:

只要增删多,就一定用 LinkedList。

七、查询和增删到底怎么比较?

这个是面试重点。

1. 查询

ArrayList 查询快。

原因:

  • 底层是数组
  • 支持随机访问
  • 可以通过下标直接定位

时间复杂度一般是:

O(1)

LinkedList 查询慢。

原因:

  • 底层是链表
  • 按下标查询需要遍历

时间复杂度一般是:

O(n)

2. 插入和删除

如果是在尾部追加:

list.add("Java");

ArrayList 通常也很快。

如果是在中间插入或删除,ArrayList 可能需要移动元素。

list.add(1, "Spring");
list.remove(1);

LinkedList 插入删除节点本身比较快,但前提是:

已经定位到要操作的节点。

如果还要先根据下标找节点,那也要遍历。

所以更准确的说法是:

ArrayList 随机访问快;
LinkedList 在已定位节点的情况下,插入删除比较方便。

八、为什么实际开发中 ArrayList 用得更多?

实际开发中,ArrayList 用得更多。

原因主要有几个:

1. 查询场景更多

很多业务代码里,集合更多是用来存一批数据,然后遍历、查询、转换。

这种场景 ArrayList 更合适。


2. 尾部追加很常见

比如:

List<UserVO> result = new ArrayList<>();

for (User user : userList) {
    result.add(convert(user));
}

这种代码很常见。

大多数时候都是往集合尾部追加元素,ArrayList 完全够用。


3. LinkedList 内存开销更大

LinkedList 每个节点都要额外存前后引用。

数据量大时,内存占用会更高。


4. LinkedList 的优势没有想象中那么大

很多人以为 LinkedList 增删一定快。

但实际场景中,如果你不是已经拿到了节点,而是通过下标找位置,再插入删除,那么它也需要遍历。

所以优势没那么绝对。


九、ArrayList 扩容简单了解一下

ArrayList 底层是数组,数组长度固定。

当元素数量超过当前数组容量时,就需要扩容。

扩容大概过程是:

  1. 创建一个更大的新数组
  2. 把旧数组元素复制过去
  3. 新元素继续添加

所以如果能预估数据量,可以提前指定容量。

比如:

List<String> list = new ArrayList<>(1000);

这样可以减少扩容次数。

面试时可以简单说:

ArrayList 底层是数组,容量不够时会扩容。扩容会创建新数组并复制旧数据,所以如果能预估数据量,可以提前指定初始容量,减少扩容成本。

十、常见追问

1. ArrayList 是线程安全的吗?

不是。

ArrayList 不是线程安全的。

多线程同时修改同一个 ArrayList,可能出现数据不一致或并发问题。

如果需要线程安全,可以考虑:

  • Collections.synchronizedList
  • CopyOnWriteArrayList
  • 自己加锁

2. LinkedList 是线程安全的吗?

也不是。

LinkedList 同样不是线程安全的。

多线程共享修改时,也需要额外保证线程安全。


3. ArrayList 和 Vector 有什么区别?

Vector 是线程安全的,很多方法加了 synchronized

但因为同步开销比较大,现在实际开发中用得比较少。

常见回答:

ArrayList 不是线程安全的,性能相对更好;
Vector 是线程安全的,但很多方法加了 synchronized,性能开销更大,现在用得比较少。

4. LinkedList 能不能当队列用?

可以。

LinkedList 实现了 Deque 接口,可以当队列或双端队列使用。

比如:

Deque<String> deque = new LinkedList<>();

deque.offer("task1");
deque.offer("task2");

System.out.println(deque.poll());

但实际开发中,如果只是做队列,也可以考虑 ArrayDeque


十一、面试回答模板

问:

ArrayList 和 LinkedList 有什么区别?

答:

ArrayList 底层是数组,支持根据下标随机访问,所以查询效率高,尾部追加元素也比较常见。但如果在中间插入或删除元素,可能需要移动后面的元素。

LinkedList 底层是双向链表,插入和删除节点本身比较方便,但它不支持快速随机访问,如果根据下标查询元素,需要从头或从尾遍历。

所以不能简单说 ArrayList 查询快、LinkedList 增删快。更准确地说,ArrayList 随机访问快;LinkedList 在已经定位到节点的情况下,插入删除比较方便。

实际开发中,ArrayList 用得更多,因为大多数场景都是查询、遍历、尾部追加,LinkedList 的优势没有想象中那么明显。

这段回答基本就够用了。

如果面试官继续追问,再展开:

  • ArrayList 扩容
  • 线程安全问题
  • Vector
  • CopyOnWriteArrayList
  • LinkedListArrayDeque

十二、一句话总结

ArrayList 底层是数组,随机访问快,适合查询和尾部追加。

LinkedList 底层是双向链表,插入删除节点本身方便,但按下标查找需要遍历。

别死背:

ArrayList 查询快,LinkedList 增删快

更准确的说法是:

ArrayList 随机访问快;LinkedList 在已定位节点的情况下,插入删除方便。

实际开发中,大多数场景优先考虑 ArrayList

大家加油:)


更多推荐