【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 核心接口说明
- Collection:所有单列集合的根接口
- Map:键值对集合的根接口
- List:有序、可重复集合
- Set:无序、不可重复集合
- Queue:队列,FIFO结构
三、List集合深度解析
3.1 ArrayList vs LinkedList
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组 | 双向链表 |
随机访问 | 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方法流程
- 计算key的hash值
- 如果数组为空,初始化
- 计算数组下标:(n-1) & hash
- 如果桶为空,直接插入
- 如果桶不为空:
- 键相同则覆盖
- 如果是树节点,调用树插入
- 否则遍历链表插入
- 判断是否需要树化
- 判断是否需要扩容
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为什么线程不安全?
参考答案:
- 多线程put可能导致元素丢失
- 扩容时可能形成循环链表(JDK7)
- 并发修改可能导致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}
}
解决方案:
- 使用迭代器的remove方法
- 使用CopyOnWriteArrayList
- 使用同步控制
六、实战编码题
题目:实现一个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)
欢迎继续在评论区交流你的面试经验和问题,我们明天见!