1. 概述

Java 集合框架基于几个核心接口,并提供了十几种不同的实现类。这种丰富的选择有时反而让人困惑。

在实际开发中,选择合适的集合类型并不是一件小事。选错了,轻则影响代码可读性,重则拖垮性能。

本文不会一次性介绍所有集合类型,而是聚焦于最常用的三种:ArrayListLinkedListHashMap。我们将从数据存储方式、性能表现以及适用场景三个方面进行剖析,帮助你做出更合理的决策。

2. 集合基础

集合本质上就是一组对象的容器。Java 集合框架(Java Collections Framework)为我们提供了一套完整的数据结构和算法,用于高效地组织和操作这些对象。

2.1. 核心接口简介

Java 集合框架的核心接口有四个:ListSetMapQueue。在深入具体实现前,理解它们的设计意图非常关键。

下面简要说明我们在本文中会涉及的三个核心接口:

  • List:有序集合,支持按索引访问和插入元素,允许重复值。
  • Map:键值对映射结构,通过唯一键来快速查找对应的值。
  • Queue:队列结构,遵循先进先出(FIFO)原则。

📌 HashMap 实现了 Map 接口;ArrayListLinkedList 实现了 List 接口;而 LinkedList 同时还实现了 Queue 接口。

2.2. List 与 Map 的使用误区

一个常见的反模式是试图用 Map 来维护顺序。这样做不仅浪费资源,也偏离了 Map 的设计初衷。

虽然单一集合类型能解决很多问题,但这并不意味着它是最佳选择。

来看个反例,有人用 Map 来保存位置相关的数据:

Map<Integer, String> map = new HashMap<>();
map.put(1, "Daniel");
map.put(2, "Marko");
for (String name : map.values()) {
    assertThat(name).isIn(map.values());
}
assertThat(map.values()).containsExactlyInAnyOrder("Daniel", "Marko");

问题在于:遍历时无法保证元素顺序,因为 Map 本身就不保证插入顺序。

换成 List 写法更合理:

List<String> list = new ArrayList<>();
list.add("Daniel");
list.add("Marko");
for (String name : list) {
    assertThat(name).isIn(list);
}
assertThat(list).containsExactly("Daniel", "Marko");

📌 Map 适合基于键快速查找,而 List 更适合维护顺序或按索引操作。

3. ArrayList

ArrayList 是 Java 中使用频率最高的 List 实现类。它基于动态数组实现,支持自动扩容和缩容。

我们可以通过索引(从 0 开始)来访问元素,也可以在末尾或指定位置插入元素:

List<String> list = new ArrayList<>();
list.add("Daniel");
list.add(0, "Marko");
assertThat(list).hasSize(2);
assertThat(list.get(0)).isEqualTo("Marko");

删除元素时可以指定索引或者对象本身:

List<String> list = new ArrayList<>(Arrays.asList("Daniel", "Marko"));
list.remove(1);
assertThat(list).hasSize(1);
assertThat(list).doesNotContain("Marko");

3.1. 性能分析

虽然 ArrayList 在底层使用数组实现,但它提供了自动扩容机制,简化了开发工作。

时间复杂度方面:

  • 随机访问(通过索引获取元素):O(1)
  • ⚠️ 插入元素:
    • 末尾插入:O(1)
    • 指定位置插入:O(n)(最坏情况需要复制整个数组)
  • ❌ 删除元素:O(n)(需要移动后续元素)
  • 🔍 查找元素:O(n)

3.2. 使用建议

如果你不确定用哪个 List 实现,ArrayList 是个不错的默认选择。

适合场景

  • 需要频繁通过索引访问元素
  • 元素顺序需要保持一致
  • 插入/删除操作集中在尾部

不适合场景

  • 频繁在中间插入/删除元素
  • 大量基于值的查找操作

4. LinkedList

LinkedList 是一种双向链表实现,同时实现了 ListDeque 接口(DequeQueue 的扩展)。每个节点都持有前驱和后继的引用。

除了常规的 List 操作,它还支持队列式操作:

LinkedList<String> list = new LinkedList<>();
list.addLast("Daniel");
list.addFirst("Marko");
assertThat(list).hasSize(2);
assertThat(list.getLast()).isEqualTo("Daniel");

支持从头或尾部删除元素:

LinkedList<String> list = new LinkedList<>(Arrays.asList("Daniel", "Marko", "David"));
list.removeFirst();
list.removeLast();
assertThat(list).hasSize(1);
assertThat(list).containsExactly("Marko");

还支持栈式操作(如 pushpoll):

LinkedList<String> list = new LinkedList<>();
list.push("Daniel");
list.push("Marko");
assertThat(list.poll()).isEqualTo("Marko");
assertThat(list).hasSize(1);

4.1. 性能分析

相比 ArrayListLinkedList 内存开销更大(每个节点有两个引用),但在插入和删除操作上更高效。

  • 插入/删除O(1)(只需修改指针)
  • 随机访问O(n)(需从头或尾遍历)
  • 🔍 查找元素:O(n)

4.2. 使用建议

虽然大多数场景下 ArrayList 是首选,但在某些特定场景中 LinkedList 更合适。

适合场景

  • 频繁在任意位置插入/删除元素
  • 需要模拟队列或栈行为

不适合场景

  • 需要频繁通过索引访问元素
  • 基于值的查找较多

5. HashMap

ArrayListLinkedList 不同,HashMap 实现了 Map 接口,存储的是键值对(key-value pairs)。每个键都唯一对应一个值。

Map<String, String> map = new HashMap<>();
map.put("123456", "Daniel");
map.put("654321", "Marko");
assertThat(map.get("654321")).isEqualTo("Marko");

删除元素也必须通过键:

Map<String, String> map = new HashMap<>();
map.put("123456", "Daniel");
map.put("654321", "Marko");
map.remove("654321");
assertThat(map).hasSize(1);

5.1. 性能分析

HashMap 之所以流行,是因为它在查找和插入操作上性能优异。

  • 通过键查找/插入/删除:平均时间复杂度为 O(1)
  • 无键查找元素O(n)(需遍历所有键值对)

虽然 HashMap 不保证顺序,也不支持索引访问,但它在大数据量下的查找性能远优于 List

5.2. 使用建议

HashMapArrayList 是 Java 中使用最频繁的两个集合类型。

适合场景

  • 数据可以通过唯一键标识
  • 需要快速查找、插入、删除操作

不适合场景

  • 需要维护插入顺序或访问顺序

6. 小结

本文深入对比了 Java 中三种常用集合类型:ArrayListLinkedListHashMap,并从性能和使用场景角度给出了建议。

  • ArrayList:适合频繁通过索引访问、尾部操作的场景
  • LinkedList:适合频繁插入/删除中间元素的场景
  • HashMap:适合通过键快速查找的场景

我们只展示了基础操作,更多高级用法可参考官方文档或相关专题文章。

📚 完整代码示例可在 GitHub 获取。


原始标题:ArrayList vs. LinkedList vs. HashMap in Java | Baeldung