1. 为什么选择牛客网刷Java集合类题目?
作为一个从零开始学习Java的开发者,我强烈推荐牛客网作为刷题平台。这个平台最大的优势在于它的题目分类系统——你可以直接找到"集合框架"专项练习区,这对新手特别友好。我刚开始学Java时,经常在各种零散题目中迷失方向,而牛客的分类系统让我能集中火力攻克特定知识点。
集合类在Java面试中的出现频率高得惊人。根据我的面试经验,大约80%的Java初级岗位技术面都会涉及集合类相关问题。Queue作为集合框架中的重要成员,经常出现在多线程、生产者消费者模式等场景的题目中。这也是为什么我们要专门用两节(40、41)来深入讲解它。
提示:刷题时建议先完成"Java集合-基础"分类下的题目,再挑战"Java集合-高级"分类,最后尝试"多线程与集合"的综合题目,这种渐进式学习效果最佳。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 集合类基础核心知识点梳理
2.1 Collection与Map的架构体系
Java集合框架主要分为两大分支:Collection和Map。很多初学者容易混淆这两者的关系,我用一个实际场景来解释:
想象你在开发一个学生管理系统:
- 用List存储按学号排列的学生名单(有序可重复)
- 用Set存储所有选修课程名称(唯一性保证)
- 用Map存储学生ID和成绩的对应关系(键值对)
java复制// 典型初始化示例
List<String> studentList = new ArrayList<>();
Set<String> courseSet = new HashSet<>();
Map<Integer, Double> scoreMap = new HashMap<>();
2.2 各实现类的性能对比
选择集合实现类时需要考虑的四大因素:
- 是否允许null元素
- 元素是否有序
- 插入/删除/查找的时间复杂度
- 线程安全性
我整理了一个关键对比表格:
| 集合类型 | 实现类 | 允许null | 有序性 | get/contains | add/remove | 线程安全 |
|---|---|---|---|---|---|---|
| List | ArrayList | 是 | 插入顺序 | O(1) | O(n) | 否 |
| List | LinkedList | 是 | 插入顺序 | O(n) | O(1) | 否 |
| Set | HashSet | 是 | 无 | O(1) | O(1) | 否 |
| Set | TreeSet | 否 | 自然排序 | O(log n) | O(log n) | 否 |
| Map | HashMap | 是 | 无 | O(1) | O(1) | 否 |
| Map | TreeMap | 否 | 键排序 | O(log n) | O(log n) | 否 |
3. Queue深度解析与实战应用
3.1 Queue的核心方法对比
Queue接口定义了6个核心方法,它们的行为差异经常成为面试考点:
java复制// 方法对比示例
Queue<String> queue = new LinkedList<>();
// 添加元素
queue.add("A"); // 满时抛异常
queue.offer("B"); // 满时返回false
// 查看队首
String s1 = queue.element(); // 空时抛异常
String s2 = queue.peek(); // 空时返回null
// 移除元素
String s3 = queue.remove(); // 空时抛异常
String s4 = queue.poll(); // 空时返回null
3.2 生产者-消费者模式实战
这是Queue最典型的应用场景。我们来看一个使用ArrayBlockingQueue的实现:
java复制class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.put(i); // 自动阻塞如果队列满
System.out.println("Produced: " + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
public void run() {
try {
while (true) {
Integer num = queue.take(); // 自动阻塞如果队列空
System.out.println("Consumed: " + num);
Thread.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 使用示例
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
注意:实际开发中应该使用线程池管理生产者和消费者线程,这里简化了示例。
4. 牛客高频考题解析
4.1 题目#JZ73:翻转单词序列
这道题可以用双端队列(Deque)优雅解决:
java复制public String ReverseSentence(String str) {
if (str == null || str.trim().isEmpty()) {
return str;
}
Deque<String> stack = new ArrayDeque<>();
for (String word : str.split(" ")) {
stack.push(word);
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop()).append(" ");
}
return sb.toString().trim();
}
4.2 题目#JZ59:滑动窗口的最大值
这是典型的单调队列应用场景:
java复制public ArrayList<Integer> maxInWindows(int[] num, int size) {
ArrayList<Integer> result = new ArrayList<>();
if (num == null || size <= 0 || num.length < size) {
return result;
}
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < num.length; i++) {
// 移除超出窗口范围的元素
while (!deque.isEmpty() && deque.peekFirst() <= i - size) {
deque.pollFirst();
}
// 维护单调递减队列
while (!deque.isEmpty() && num[deque.peekLast()] < num[i]) {
deque.pollLast();
}
deque.offerLast(i);
// 记录窗口最大值
if (i >= size - 1) {
result.add(num[deque.peekFirst()]);
}
}
return result;
}
5. 刷题进阶技巧与避坑指南
5.1 集合类常见面试陷阱
-
fail-fast机制:在迭代集合时修改集合会抛出ConcurrentModificationException
- 解决方案:使用Iterator的remove方法或CopyOnWriteArrayList
-
equals与hashCode的契约:作为HashMap键的对象必须正确重写这两个方法
- 典型错误:只重写equals但没重写hashCode
-
自动装箱陷阱:在集合中使用基本类型时发生的性能问题
java复制// 反例 - 产生大量Integer对象 List<Integer> list = new ArrayList<>(); for (int i = 0; i < 1000000; i++) { list.add(i); // 自动装箱 }
5.2 性能优化实战建议
-
初始化指定容量:对于已知大小的集合,初始化时指定容量避免扩容开销
java复制// 好于默认构造 List<String> list = new ArrayList<>(1000); Map<String, Integer> map = new HashMap<>(1024); -
使用EnumSet替代位域:当元素是枚举类型时,EnumSet是最高效的实现
java复制enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN } Set<Weekday> weekend = EnumSet.of(Weekday.SAT, Weekday.SUN); -
并行流处理大数据集:Java 8+的并行流可以充分利用多核优势
java复制List<Integer> numbers = /* 大数据集 */; int sum = numbers.parallelStream().mapToInt(i -> i).sum();
6. 从刷题到项目实战的过渡
当你能熟练解决牛客上的集合类题目后,可以尝试将这些知识应用到实际项目中。我推荐以下几个练手方向:
-
实现一个LRU缓存:结合LinkedHashMap的特性
java复制class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; } } -
设计一个事件总线:使用ConcurrentHashMap存储事件监听器
java复制class EventBus { private final ConcurrentHashMap<Class<?>, CopyOnWriteArrayList<Consumer<?>>> listeners = new ConcurrentHashMap<>(); public <T> void subscribe(Class<T> eventType, Consumer<T> listener) { listeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add(listener); } public <T> void publish(T event) { @SuppressWarnings("unchecked") List<Consumer<T>> eventListeners = (List<Consumer<T>>) (List<?>) listeners.get(event.getClass()); if (eventListeners != null) { eventListeners.forEach(l -> l.accept(event)); } } } -
构建一个轻量级工作流引擎:使用PriorityQueue管理任务优先级
java复制class WorkflowEngine { private final PriorityQueue<Task> taskQueue = new PriorityQueue<>( Comparator.comparingInt(Task::getPriority).reversed() ); public void addTask(Task task) { taskQueue.offer(task); } public void processTasks() { while (!taskQueue.isEmpty()) { Task task = taskQueue.poll(); task.execute(); } } }
在实际开发中,集合类的选择往往需要权衡多种因素。比如在高并发场景下,ConcurrentHashMap通常比Collections.synchronizedMap更优;而在读多写少的场景,CopyOnWriteArrayList可能比Vector更合适。这些经验需要在不断的实践中积累。
