```html
欢迎访问 kaoacca.cn - 专注Java技术面试解析 域名:www.kaoacca.cn

Java集合面试题

深度解析集合框架底层原理 · 高频考点一网打尽 · 源码级代码示例

Java集合框架概览与核心架构

在Java开发中,Java集合面试题是面试中占比极高的部分。Java集合框架(Java Collections Framework, JCF)提供了高度优化的数据结构实现,包括列表(List)、集合(Set)和映射(Map)。理解其底层实现不仅是应对面试的关键,更是编写高性能Java代码的基础。

Java集合主要分为两大接口体系:一是继承自 Collection 接口的单列集合,如 ArrayList、LinkedList 和 HashSet;二是继承自 Map 接口的双列集合,如 HashMap、TreeMap 和 ConcurrentHashMap。面试官通常关注这些集合的线程安全性、扩容机制以及底层数据结构。

⚡ Collection 接口

  • List:有序、可重复。实现类包括 ArrayList, LinkedList, Vector。
  • Set:无序、不可重复。实现类包括 HashSet, LinkedHashSet, TreeSet。
  • Queue:队列结构。实现类包括 PriorityQueue, ArrayDeque。

⚙️ Map 接口

  • HashMap:基于哈希表,无序,非线程安全,允许null键值。
  • TreeMap:基于红黑树,有序(自然排序或自定义排序),非线程安全。
  • ConcurrentHashMap:线程安全,JDK7分段锁,JDK8 CAS+synchronized。

HashMap 底层原理与源码深度解析

HashMap 是Java集合面试题中的“常客”。它基于哈希表实现,提供了常量时间复杂度的 get 和 put 操作。理解HashMap的关键在于掌握其数据结构演变、哈希冲突解决机制以及扩容机制。

1. 数据结构演变

在JDK 1.7中,HashMap由数组+链表组成。而在JDK 1.8中,为了优化性能,引入了数组+链表+红黑树的结构。当链表长度超过8且数组长度超过64时,链表会转换为红黑树,将查找时间复杂度从 O(n) 降低到 O(log n)。

2. 哈希冲突解决:链地址法

HashMap通过计算Key的哈希值来确定存储位置。当不同Key计算出相同哈希值(即哈希冲突)时,HashMap采用链地址法解决冲突,即在同一个数组位置上形成一个链表。若冲突严重,则升级为红黑树。

3. 扩容机制(Resize)

HashMap的默认初始容量为16,负载因子为0.75。当元素数量超过 16 0.75 = 12 时,HashMap会触发扩容,容量变为原来的2倍(32),并重新计算所有元素的位置。扩容是一个耗时操作,因此在预知数据量时,建议初始化时指定容量。

// 模拟HashMap put操作的核心逻辑(简化版)
public V put(K key, V value) {
    if (key == null) return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    // 遍历链表/树,若key存在则覆盖
    for (Entry e = table[i]; e != null; e = e.next) {
        if (e.hash == hash && ((e.key == key) || key.equals(e.key))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

4. 为什么Key对象需要重写 hashCode() 和 equals()?

这是Java集合面试题中的经典问题。HashMap依赖 hashCode() 来确定桶的位置,依赖 equals() 来判断Key是否相同。如果不重写这两个方法,默认使用 Object 类的实现(基于内存地址),导致逻辑上相同的对象被视为不同的Key,造成数据丢失或重复存储。

List 集合核心对比:ArrayList vs LinkedList

ArrayList 和 LinkedList 是List接口的两个主要实现,它们在面试中常被拿来对比。选择哪种集合取决于具体的应用场景:是频繁查询还是频繁插入删除。

特性 ArrayList LinkedList
底层数据结构 动态数组(Object[]) 双向链表
随机访问(get) O(1),速度快 O(n),需要遍历
插入/删除(中间) O(n),需移动大量元素 O(1),只需修改指针(若已知节点)
内存占用 较低,仅存储数据 较高,每个节点存储前后指针
线程安全性 非线程安全 非线程安全

ArrayList 扩容细节

ArrayList默认容量为10。当容量不足时,新容量 = 旧容量 + (旧容量 >> 1),即扩容为原来的1.5倍。JDK 1.8中,如果初始容量为0,首次扩容时会扩容为10,二次扩容再按1.5倍增长。这种策略平衡了内存使用和扩容频率。

并发集合专题:ConcurrentHashMap 线程安全机制

在多线程环境下,HashMap 是不安全的,可能导致数据覆盖或死循环(JDK 1.7)。因此,面试中常问如何实现线程安全的Map,答案通常是 ConcurrentHashMap。

JDK 1.7 分段锁
JDK 1.8 CAS+synchronized

Segment 分段锁机制

在JDK 1.7中,ConcurrentHashMap由Segment数组和HashEntry数组组成。Segment继承了ReentrantLock,充当锁的角色。默认有16个Segment,支持最多16个线程并发写操作。这种设计大大提高了并发度,但内存占用较高。

CAS + synchronized 节点锁

JDK 1.8 抛弃了Segment,采用Node数组+链表+红黑树的结构。锁粒度更细,仅锁住链表或红黑树的头节点。使用CAS(Compare And Swap)操作进行无锁化的插入,若插入时发生冲突,则使用 synchronized 锁住头节点。这种方式在减少内存占用的同时,保持了高并发性能。

Hashtable 与 Collections.synchronizedMap 的区别

Hashtable 是古老的线程安全Map,所有操作都加 synchronized 锁,粒度粗,性能差。Collections.synchronizedMap 是将普通Map包装起来,所有方法都加锁,同样存在性能瓶颈。相比之下,ConcurrentHashMap 通过细粒度锁或CAS机制,提供了更高的并发性能。

迭代器(Iterator)与集合遍历陷阱

遍历集合是日常开发中最常见的操作,但其中隐藏着许多陷阱,尤其是并发修改异常(ConcurrentModificationException)。

1. 快速失败(Fail-Fast)机制

在使用Iterator遍历集合时,如果集合结构被修改(添加或删除元素),Iterator会抛出 ConcurrentModificationException。这是由 modCount 字段实现的。Iterator在创建时记录当前的 modCount,每次调用 next() 时检查是否一致。如果不一致,说明集合被外部修改了。

// 错误示例:在遍历中直接删除元素
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
    if ("A".equals(s)) {
        list.remove(s); // 触发 ConcurrentModificationException
    }
}
// 正确示例:使用 Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if ("A".equals(s)) {
        it.remove(); // 安全删除,同步更新 modCount
    }
}

2. 增强for循环的本质

Java的增强for循环(foreach)底层实际上是使用Iterator实现的。因此,在增强for循环中也不应直接修改集合结构。

Java集合面试题高频问答 FAQ

以下是求职者最常遇到的Java集合相关问题,以及深度解答。

Q1: HashMap 和 HashTable 有什么区别?

1. 线程安全:Hashtable是线程安全的,HashMap非线程安全。
2. null值支持:HashMap允许一个null键和多个null值,Hashtable不允许任何null键或值。
3. 性能:HashMap性能更高,因为无锁机制。
4. 迭代器:HashMap的Iterator是fail-fast的,Hashtable的Enumeration不是。

Q2: HashSet 是如何保证元素不重复的?

HashSet底层是基于HashMap实现的。当向HashSet中添加元素时,实际上是将该元素作为Key存入HashMap的Value为PRESENT(一个静态常量Object对象)。由于HashMap的Key不能重复,因此HashSet的元素也不能重复。这依赖于元素的 hashCode() 和 equals() 方法。

Q3: 如何对自定义对象进行排序?

有两种方式:
1. 实现 Comparable 接口,重写 compareTo() 方法,定义自然排序。
2. 创建 Comparator 实现类,重写 compare() 方法,在排序时传入比较器。这种方式更灵活,支持多种排序策略。

Q4: ArrayDeque 和 LinkedList 有什么区别?

ArrayDeque底层基于动态数组,LinkedList基于双向链表。ArrayDeque作为栈或队列使用时,性能通常优于LinkedList,因为它没有链表节点的额外内存开销,且缓存命中率更高。ArrayDeque不允许null元素。

```
斌知网
蜀ICP备2026017620号-5