JavaGuide项目:Java集合框架核心知识点与面试题解析(上)
作为Java开发者,掌握集合框架是基本功。本文将系统梳理Java集合框架的核心知识点,帮助读者深入理解各种集合类的特性和使用场景。
一、Java集合框架概述
1.1 集合框架体系结构
Java集合框架主要由两大接口派生而来:
-
Collection接口:存放单一元素,主要子接口包括:
- List:有序、可重复
- Set:无序、不可重复
- Queue:队列结构
-
Map接口:存放键值对(key-value)
集合框架的层级关系如下图所示(简化版):
Collection
├── List
│ ├── ArrayList
│ ├── LinkedList
│ └── Vector
├── Set
│ ├── HashSet
│ ├── LinkedHashSet
│ └── TreeSet
└── Queue
├── PriorityQueue
└── ArrayDeque
Map
├── HashMap
├── LinkedHashMap
├── TreeMap
└── Hashtable
1.2 集合选型指南
选择集合时应考虑以下因素:
- 元素特性:是否需要有序?是否允许重复?
- 性能需求:插入、删除、查询操作的频率
- 线程安全:是否需要并发支持
- 特殊需求:是否需要排序、是否频繁修改
二、List集合详解
2.1 ArrayList vs 数组
ArrayList基于动态数组实现,与静态数组相比具有明显优势:
| 特性 | ArrayList | 数组 | |-------------|--------------------------|--------------------------| | 长度可变性 | 动态扩容/缩容 | 固定长度 | | 类型安全 | 支持泛型 | 不支持泛型 | | 存储类型 | 只能存储对象 | 可存储基本类型和对象 | | API丰富度 | 提供丰富操作方法 | 仅支持下标访问 | | 初始化 | 无需指定大小 | 必须指定大小 |
2.2 ArrayList vs LinkedList
两者都是List接口的实现,但底层结构和性能特点迥异:
| 特性 | ArrayList | LinkedList | |---------------------|--------------------------------|--------------------------------| | 底层结构 | 动态数组 | 双向链表 | | 随机访问 | O(1) | O(n) | | 头部插入/删除 | O(n) | O(1) | | 尾部插入/删除 | O(1) | O(1) | | 指定位置插入/删除 | O(n) | O(n) | | 内存占用 | 预留空间 | 每个元素额外存储前后指针 |
实践建议:大多数场景下优先使用ArrayList,仅在频繁操作头尾元素时考虑LinkedList。
2.3 ArrayList扩容机制
ArrayList的扩容是其核心机制:
- 默认初始容量为10
- 添加元素时检查容量,不足则触发扩容
- 新容量 = 旧容量 * 1.5
- 扩容通过Arrays.copyOf实现数据迁移
扩容操作的时间复杂度为O(n),但通过均摊分析,插入操作的均摊时间复杂度仍为O(1)。
2.4 Vector与Stack
这两个是Java早期的集合实现,现已不推荐使用:
- Vector:线程安全的动态数组,方法使用synchronized修饰
- Stack:继承自Vector的后进先出(LIFO)栈
现代Java开发中,推荐使用:
- 并发场景:CopyOnWriteArrayList
- 栈结构:ArrayDeque
三、Set集合详解
3.1 Set核心特性
Set集合的两大特性:
- 无序性:元素存储顺序不由插入顺序决定,而是由哈希值决定
- 不可重复性:依赖equals()和hashCode()方法判断元素相等
3.2 HashSet、LinkedHashSet、TreeSet对比
| 特性 | HashSet | LinkedHashSet | TreeSet | |---------------|------------------|-----------------------|------------------| | 底层实现 | HashMap | LinkedHashMap | 红黑树 | | 排序方式 | 无 | 插入顺序 | 自然/定制排序 | | 时间复杂度 | O(1) | O(1) | O(log n) | | 线程安全 | 否 | 否 | 否 | | 允许null | 是 | 是 | 否 |
使用场景:
- 只需去重:HashSet
- 需要保持插入顺序:LinkedHashSet
- 需要排序:TreeSet
3.3 Comparable与Comparator
两者都用于对象排序,但有以下区别:
| 特性 | Comparable | Comparator | |-----------------|-----------------------------|-----------------------------| | 包位置 | java.lang | java.util | | 方法 | compareTo(T o) | compare(T o1, T o2) | | 使用场景 | 定义对象的自然排序 | 定义多种排序方式 | | 侵入性 | 需要修改类 | 无需修改类 |
示例:
// 使用Comparator定义降序排序
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
// 实现Comparable接口
class Person implements Comparable<Person> {
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
}
四、Queue与Deque
4.1 Queue与Deque的区别
Queue是单端队列,Deque是双端队列:
| 操作 | Queue方法 | Deque新增方法 | |------------|----------------|---------------------| | 插入队尾 | add(e)/offer(e) | addLast(e)/offerLast(e) | | 插入队首 | - | addFirst(e)/offerFirst(e) | | 删除队首 | remove()/poll() | removeFirst()/pollFirst() | | 删除队尾 | - | removeLast()/pollLast() |
4.2 ArrayDeque vs LinkedList
两者都可作为Deque实现,但各有特点:
| 特性 | ArrayDeque | LinkedList | |---------------|-------------------------|------------------------| | 底层结构 | 可变长数组+双指针 | 双向链表 | | 允许null | 否 | 是 | | 内存占用 | 连续内存 | 分散内存 | | 插入性能 | 均摊O(1),可能扩容 | 每次申请新节点 | | 适用场景 | 高频队列操作 | 需要null值或频繁修改 |
最佳实践:大多数队列场景优先使用ArrayDeque,它同时也能很好地作为栈使用。
五、Fail-Fast与Fail-Safe机制
5.1 Fail-Fast机制
原理:
- 维护modCount计数器记录修改次数
- 迭代时检查modCount是否变化
- 发现并发修改立即抛出ConcurrentModificationException
示例:
List<String> list = new ArrayList<>();
// 迭代过程中修改集合
for (String s : list) {
list.remove(s); // 抛出ConcurrentModificationException
}
5.2 Fail-Safe机制
原理:
- 基于集合副本进行操作
- 修改不影响正在进行的迭代
- 典型实现:CopyOnWriteArrayList
特点:
- 不会抛出并发修改异常
- 数据一致性较弱(看到的是操作前的快照)
- 内存开销较大(需要维护副本)
5.3 对比总结
| 特性 | Fail-Fast | Fail-Safe | |---------------|-------------------------|-------------------------| | 并发修改 | 抛出异常 | 继续执行 | | 实现方式 | 修改计数检查 | 数据副本 | | 性能 | 无额外开销 | 需要复制数据 | | 一致性 | 强一致 | 最终一致 | | 典型实现 | ArrayList, HashMap | CopyOnWriteArrayList |
理解这些集合特性和机制,将帮助开发者在实际项目中做出更合理的技术选型,并编写出更健壮的代码。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考