Java 集合框架核心知识点全解析:从入门到高频面试题(含 JDK 源码剖析)
一、Java 集合框架体系架构
Java 集合框架分为两大分支:
Collection
接口:存储单个元素,包括:List
:有序、可重复(如ArrayList
、LinkedList
)Set
:无序、唯一(如HashSet
、TreeSet
)Queue
:队列(如PriorityQueue
、LinkedBlockingQueue
)
Map
接口:存储键值对(如HashMap
、ConcurrentHashMap
)
核心设计思想:
- 接口层定义操作规范(如
add()
、get()
) - 实现层提供不同数据结构的具体实现
- 工具层(
Collections
、Arrays
)提供排序、同步等辅助方法
二、核心接口与实现类深度解析
2.1 List 家族:有序数据的存储方案
2.1.1 ArrayList 源码剖析
- 底层实现:基于动态数组(
Object[] elementData
),支持随机访问 - 扩容机制:
- 初始容量为
10
(JDK1.8 后),扩容时按1.5 倍增长 - 源码核心逻辑:
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容 elementData = Arrays.copyOf(elementData, newCapacity); // 数组复制
- 初始容量为
- 适用场景:适合高频随机访问(如
get(index)
),不适合频繁中间插入 / 删除
2.1.2 LinkedList 双向链表实现
- 数据结构:双向链表,每个节点包含
prev
、next
指针 - 核心优势:
- 头尾操作高效(
addFirst()
、removeLast()
均为 O (1)) - 无需连续内存,适合频繁增删场景
- 头尾操作高效(
- 缺点:随机访问需遍历链表(
get(index)
为 O (n))
2.2 Set 家族:唯一性与排序策略
2.2.1 HashSet 存储原理
- 唯一性实现:
- 通过
hashCode()
计算元素哈希值,确定存储桶位置 - 在桶内通过
equals()
方法对比元素是否重复
- 通过
- JDK8 优化:
- 当链表长度≥8 且数组长度≥64 时,链表转为红黑树(提升查询效率至 O (log n))
- 注意:若自定义类存入
HashSet
,需重写hashCode()
和equals()
方法
2.2.2 TreeSet 排序规则
- 自然排序:元素需实现
Comparable
接口(如Integer
、String
) - 定制排序:通过
Comparator
接口指定排序规则// 降序排列整数 TreeSet<Integer> ts = new TreeSet<>(Comparator.reverseOrder());
2.3 Map 家族:键值对的高效检索
2.3.1 HashMap 线程不安全问题
- 多线程风险:
- JDK7 中采用头插法扩容,多线程下可能形成循环链表,导致死锁
- JDK8 改为尾插法,但仍不保证线程安全
- 解决方案:
- 使用
ConcurrentHashMap
(推荐) - 通过
Collections.synchronizedMap()
包装(全表锁,性能较低)
- 使用
2.3.2 ConcurrentHashMap 的锁优化
- JDK7:分段锁(
Segment
数组,默认 16 个分段,并发度 16) - JDK8:
- 采用
CAS+synchronized
锁定单个节点(锁粒度更细) - 链表转红黑树优化查询效率
// JDK8 put操作关键代码 synchronized (f) { // 锁定当前桶的头节点if (f.hash == hash && f.key == key) {// 处理键存在的情况} }
- 采用
三、高频面试题与最佳实践
3.1 集合选型对比表
需求场景 | 优先选择 | 时间复杂度 | 线程安全 |
---|---|---|---|
高频随机访问 | ArrayList | O (1)(访问) | 否 |
高频首尾增删 | LinkedList | O (1)(首尾操作) | 否 |
唯一无序集合 | HashSet | O (1)(插入 / 查询) | 否 |
有序键值对排序 | TreeMap | O(log n) | 否 |
高并发读写 | ConcurrentHashMap | O (1)(平均) | 是 |
3.2 性能优化实战
- 预分配容量:
// 避免ArrayList多次扩容 List<String> list = new ArrayList<>(100); // 初始容量100 // 避免HashMap频繁rehash Map<String, Integer> map = new HashMap<>(16); // 初始容量16(需满足初始数据量/负载因子)
- 遍历优化:
- ArrayList:普通
for
循环(直接通过下标访问) - LinkedList:使用
ListIterator
双向遍历 - Map:优先使用
entrySet()
而非keySet()
(减少键值对拆分开销)
- ArrayList:普通
四、JDK 新特性深度解读
4.1 Java8 Stream API 实战
// 案例:过滤长字符串并转大写
List<String> result = list.stream().filter(s -> s.length() > 3) // 过滤长度>3的元素.map(String::toUpperCase) // 转大写.collect(Collectors.toList()); // 收集结果// 案例:按首字母分组统计
Map<Character, List<String>> groupMap = list.stream().collect(Collectors.groupingBy(s -> s.charAt(0)));
4.2 Java9 不可变集合工厂
// 创建不可变List(元素不可修改,不允许null)
List<String> immutableList = List.of("A", "B", "C");
// 创建不可变Map(键值均不可修改)
Map<String, Integer> map = Map.of("a", 1, "b", 2);
五、常见陷阱与避坑指南
5.1 并发修改异常(ConcurrentModificationException)
错误场景:
List<String> list = new ArrayList<>();
list.add("1");
for (String s : list) { // 底层使用Iterator,modCount校验机制if (s.equals("1")) {list.remove(s); // 触发modCount不一致,抛出异常}
}
正确做法:使用迭代器的remove()
方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();if (s.equals("1")) {it.remove(); // 安全删除}
}
5.2 哈希值与相等性陷阱
// 自定义类未重写hashCode/equals的后果
class User {private String id;// 未重写hashCode和equals
}Set<User> set = new HashSet<>();
set.add(new User("1"));
System.out.println(set.contains(new User("1"))); // 输出false(哈希值不同)
解决方案:
@Override
public int hashCode() {return Objects.hash(id);
}@Override
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;return Objects.equals(id, user.id);
}
六、总结与学习路线
6.1 核心知识图谱
- 数据结构:数组、链表、哈希表、红黑树的应用场景
- 线程安全:
Vector
/HashTable
的缺陷,ConcurrentHashMap
的锁优化 - 性能优化:容量预分配、遍历方式选择、哈希函数设计
6.2 学习建议
- 源码精读:优先阅读
ArrayList
、HashMap
、ConcurrentHashMap
的源码 - 工具辅助:使用
Java VisualVM
分析集合内存占用,JMH
进行性能基准测试 - 算法实践:通过 LeetCode 题目(如两数之和、字母异位词分组)强化集合应用
本文涵盖 Java 集合框架的核心原理、面试高频考点及实战优化,建议收藏并结合源码练习加深理解。如有疑问,欢迎在评论区留言讨论! 🚀