当前位置: 首页 > news >正文

【Java工程师面试全攻略】Day2:Java集合框架面试全解析

一、昨日回顾与今日预告

在昨天的开篇文章中,我们了解了Java工程师面试的整体流程和基础准备方向。今天我们将深入Java集合框架,这是面试中出现频率最高的技术点之一。据不完全统计,90%以上的Java技术面试都会涉及集合相关的问题。

二、集合框架整体架构

2.1 Java集合框架类图

Collection
├── List
│   ├── ArrayList
│   ├── LinkedList
│   └── Vector
│       └── Stack
├── Set
│   ├── HashSet
│   │   └── LinkedHashSet
│   └── TreeSet
└── Queue├── PriorityQueue└── Deque└── ArrayDequeMap
├── HashMap
│   └── LinkedHashMap
├── TreeMap
└── Hashtable└── Properties

2.2 核心接口说明

  1. Collection:所有单列集合的根接口
  2. Map:键值对集合的根接口
  3. List:有序、可重复集合
  4. Set:无序、不可重复集合
  5. Queue:队列,FIFO结构

三、List集合深度解析

3.1 ArrayList vs LinkedList

特性ArrayListLinkedList
底层结构动态数组双向链表
随机访问O(1)O(n)
头部插入O(n)O(1)
尾部插入O(1)O(1)
内存占用较少较多(节点开销)
适用场景查询多,增删少增删多,查询少

源码解析:ArrayList扩容机制

// ArrayList的grow方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}

3.2 Vector与线程安全

Vector通过synchronized方法保证线程安全,但性能较差。现代Java开发中更推荐使用:

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 或者
CopyOnWriteArrayList<String> copyOnWriteList = new CopyOnWriteArrayList<>();

四、Map集合核心剖析

4.1 HashMap实现原理

JDK8的HashMap结构:数组+链表+红黑树

[0] → null
[1] → Node<K,V> → Node<K,V> → TreeNode<K,V>
[2] → null
...
[n] → Node<K,V>

关键参数:

  • 默认初始容量:16
  • 负载因子:0.75
  • 树化阈值:8
  • 退化阈值:6

4.2 HashMap的put方法流程

  1. 计算key的hash值
  2. 如果数组为空,初始化
  3. 计算数组下标:(n-1) & hash
  4. 如果桶为空,直接插入
  5. 如果桶不为空:
    • 键相同则覆盖
    • 如果是树节点,调用树插入
    • 否则遍历链表插入
  6. 判断是否需要树化
  7. 判断是否需要扩容

4.3 ConcurrentHashMap并发控制

JDK8实现改进:

  • 取消分段锁
  • 使用CAS+synchronized
  • 链表长度超过8时转为红黑树
// ConcurrentHashMap的putVal方法片段
final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable(); // 初始化表else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))break; // CAS插入}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f); // 协助扩容else {// synchronized锁住链表头节点synchronized (f) {// 链表或树插入操作}}}addCount(1L, binCount);return null;
}

五、高频面试题解析

5.1 问题1:HashMap为什么线程不安全?

参考答案:

  1. 多线程put可能导致元素丢失
  2. 扩容时可能形成循环链表(JDK7)
  3. 并发修改可能导致fast-fail

解决方案:

  • 使用ConcurrentHashMap
  • 使用Collections.synchronizedMap
  • 使用Hashtable(不推荐)

5.2 问题2:ArrayList的迭代器fail-fast机制

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();if (s.equals("b")) {list.remove("b"); // 抛出ConcurrentModificationException}
}

解决方案:

  1. 使用迭代器的remove方法
  2. 使用CopyOnWriteArrayList
  3. 使用同步控制

六、实战编码题

题目:实现一个LRU缓存

public class LRUCache<K, V> extends LinkedHashMap<K, V> {private final int capacity;public LRUCache(int capacity) {super(capacity, 0.75f, true);this.capacity = capacity;}@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size() > capacity;}public static void main(String[] args) {LRUCache<Integer, String> cache = new LRUCache<>(3);cache.put(1, "A");cache.put(2, "B");cache.put(3, "C");cache.get(1);    // 访问1,使其成为最近使用的cache.put(4, "D"); // 2会被移除System.out.println(cache); // 输出 {3=C, 1=A, 4=D}}
}

七、明日预告

明天我们将探讨《Java并发编程面试精要》,内容包括:

  • 线程生命周期与状态转换
  • synchronized和ReentrantLock的实现原理
  • volatile关键字的内存语义
  • AQS框架与并发工具类
  • 线程池的核心参数与工作原理

八、昨日思考题答案

问题1:final关键字用法

  • final类:不可继承
  • final方法:不可重写
  • final变量:不可修改(基本类型值不可变,引用类型指向不可变)

问题2:Integer缓存问题

Integer a = 100, b = 100;
System.out.println(a == b); // true,使用缓存
Integer c = 200, d = 200;
System.out.println(c == d); // false,超出缓存范围(-128~127)

欢迎继续在评论区交流你的面试经验和问题,我们明天见!

http://www.xdnf.cn/news/652663.html

相关文章:

  • 榕壹云物品回收系统实战案例:基于ThinkPHP+MySQL+UniApp的二手物品回收小程序开发与优化
  • 【运维】OpenWrt DNS重绑定保护配置指南:解决内网域名解析问题
  • 项目亮点 封装request请求模块
  • 2025年- H51-Lc159 --199. 二叉树的右视图(层序遍历,队列)--Java版
  • AI学习笔记二十八:使用ESP32 CAM和YOLOV5实现目标检测
  • 使用docker容器部署Elasticsearch和Kibana
  • Rk3568 Andorid 11 ,根据prop属性的值控制是否禁止u盘连接
  • 倚光科技在二元衍射面加工技术上的革新:引领光学元件制造新方向​
  • 拓扑光子混沌算法
  • 开源第三方库发展现状
  • 《软件工程》第 9 章 - 软件详细设计
  • Ini配置文件读写,增加备注功能
  • VR 技术在农业领域或许是一抹新曙光​
  • Java Class 文件编码机制全解析
  • 分布式锁与锁续期
  • 轻量级视觉语言模型 Dolphin:高效精准的文档结构化解析利器
  • 电机控制学习笔记
  • 深入解析Spring Boot与Spring Security整合实现JWT认证
  • ADS学习笔记(四) S参数仿真
  • 网络编程1
  • SAP ERP 系统拆分的七大挑战
  • WIN--文件读写
  • Linux的top命令使用
  • 在前端项目中实现打包后可配置地址(如 API 域名、静态资源路径等)
  • 告别复杂操作!链抽象如何让 Web3 用户体验媲美 Web2?
  • Element UI 对话框固定宽度 + 遮罩层深度定制方案
  • 零基础设计模式——结构型模式 - 适配器模式
  • 基于 docker 部署 k8s 集群
  • 机器学习中的线性回归:从理论到实践的深度解析
  • 运行comfyui Wan2.1 文生视频工作流,问题总结