Skip to content

集合面试题:选对数据结构是基本功

集合是 Java 里最常用的 API 之一,也是面试必问。

很多人在准备面试时把各种集合的源码背了一遍,但真正被问到:"我们系统里有个场景是高并发写入、随机读取,你觉得该用哪个?" 就答不上来了。

光背源码没用,得理解每个集合的设计意图和适用场景。

Java 集合框架全览

Collection
├── List(有序、可重复)
│   ├── ArrayList        → 数组实现,随机访问 O(1)
│   ├── LinkedList       → 链表实现,插入删除 O(1)
│   └── Vector           → 同步版 ArrayList(已被弃用)
└── Set(无序、去重)
    ├── HashSet          → 哈希表,O(1) 查找/插入
    ├── TreeSet          → 红黑树,有序
    └── LinkedHashSet    → 保留插入顺序的 HashSet

Map(键值对)
├── HashMap              → O(1) 查找/插入
├── TreeMap              → 红黑树,有序
├── LinkedHashMap        → 保留插入顺序
└── Hashtable            → 同步版 HashMap(已被弃用)

ArrayList vs LinkedList

对比ArrayListLinkedList
随机访问O(1)O(n),需遍历
头部插入O(n),需移动后面所有元素O(1)
中间插入O(n)O(1),但需先遍历到位置
内存连续,节省指针开销分散,需要额外存储指针
CPU 缓存友好,连续内存访问快不友好

结论:90% 的场景用 ArrayList,只有在大量头插/尾删时才考虑 LinkedList。

HashMap 原理:必问

HashMap 的底层是数组 + 链表(JDK 8+ 红黑树)

当发生哈希冲突时,冲突的元素以链表形式挂在数组的同一位置。当链表长度超过 8 时,转化为红黑树(O(log n) vs O(n))。

核心参数:

参数默认值说明
capacity16数组大小,必须是 2 的幂次
loadFactor0.75负载因子,超过就扩容
threshold12触发扩容的阈值(capacity * loadFactor)

扩容机制:容量翻倍(16 → 32 → 64...),重新计算每个元素的索引位置。

ConcurrentHashMap:线程安全

HashMap 线程不安全,并发下会死循环(JDK 7 的扩容机制)。多线程环境用 ConcurrentHashMap:

java
// ❌ 线程不安全
Map<String, Integer> map = new HashMap<>();

// ✅ 线程安全
Map<String, Integer> safe1 = new ConcurrentHashMap<>();

// ✅ 也安全,但不推荐(全表锁)
Map<String, Integer> safe2 = Collections.synchronizedMap(new HashMap<>());

集合转换

java
List<String> list = Arrays.asList("a", "b", "c");

// List → Set(去重)
Set<String> set = new HashSet<>(list);

// List → Map
Map<String, Integer> map = list.stream()
    .collect(Collectors.toMap(s -> s, String::length));

// 过滤
List<String> filtered = list.stream()
    .filter(s -> s.length() > 1)
    .collect(Collectors.toList());

总结

选集合的核心:

  • 随机访问多 → ArrayList
  • 频繁插入删除 → LinkedList(仅限头尾)
  • 键值对 → HashMap
  • 多线程 → ConcurrentHashMap
  • 需要排序 → TreeMap / TreeSet

没有最好的集合,只有最合适的。

基于 VitePress 构建