主题
面试题蒸馏卡:Java 集合
从
github-hxq-note/Java/Java util+语雀-Java开发/JavaSE/集合9 篇素材提炼的高频面试题。每题按「标准答 → 追问链 → 常见误解」组织。
Q1:HashMap 的底层原理?
标准答:底层是「数组 + 链表 + 红黑树」。哈希函数 (h=hashCode())^(h>>>16) 算 hash,hash&(length-1) 算索引定位到数组桶;冲突用链表,链表长度 ≥8 且数组 ≥64 时转红黑树(查询 O(n)→O(logn))。
追问链:
- "为什么数组长度是 2 的 n 次方?" → length-1 低位全 1,& 运算分布均匀、碰撞小,且位运算快
- "为什么负载因子 0.75?" → 时间空间折衷:太小频繁扩容、太大冲突多
- "链表转红黑树条件?" → 两个都要满足:链表 ≥8 且 table ≥64,否则优先扩容
常见误解:❌ 以为链表一超 8 就树化(还得 ≥64);❌ 以为 HashMap 线程安全。
Q2:HashMap 1.7 和 1.8 的区别?
标准答:三处优化——① 数据结构加红黑树;② 插入从头插法改尾插法;③ 扩容不用重新哈希,位置「不变或 +旧容量」。
追问链:
- "1.7 为什么死循环?" → 头插法 transfer 迁移时链表翻转,多线程形成环形链表
- "1.8 为什么还会数据覆盖?" → 并发 put 同时判断桶为空都插入,或 ++size 竞争
- "并发场景用什么?" → ConcurrentHashMap
常见误解:❌ 以为 1.8 就线程安全了(只是不再死循环,数据覆盖还在)。
Q3:ConcurrentHashMap 1.7 和 1.8 的区别?
标准答:核心是「锁粒度变细」——1.7 用 Segment 分段锁(默认 16 段,继承 ReentrantLock),1.8 改 CAS + synchronized(锁 Node) + 红黑树 + 多线程扩容。
追问链:
- "1.8 为什么 get 不用加锁?" → Node 数组 volatile + Unsafe 保证可见性
- "sizeCtl 是什么?" → 控制初始化/扩容(-1 初始化、-n 有 n-1 线程帮扩容、正数阈值)
- "1.8 怎么统计 size?" → counterCells 分散热点(同 LongAdder),求和
- "为什么 CHM 不允许 null?" → 并发下无法区分「不存在」和「值为 null」
常见误解:❌ 以为 1.8 还是分段锁;❌ 以为 CHM 读要加锁。
Q4:ArrayList 和 LinkedList 区别?
标准答:ArrayList 是动态数组,随机访问 O(1) 快、中间增删 O(n) 慢;LinkedList 是双向链表,增删 O(1) 快、随机访问 O(n) 慢。ArrayList 实现 RandomAccess,LinkedList 还实现 Deque。
追问链:
- "ArrayList 扩容机制?" → 无参构造首次 add 扩到 10,之后 1.5 倍(oldCapacity + oldCapacity>>1)
- "fail-fast 是什么?" → modCount 记录结构修改,遍历中改结构抛 ConcurrentModificationException
- "什么时候用 LinkedList?" → 频繁头尾增删/当队列栈用;否则 ArrayList 更优(内存局部性)
常见误解:❌ 以为 LinkedList 增删一定快(中间增删要先遍历定位);❌ 以为 ArrayList 初始 10 容量(是懒初始化)。
Q5:HashMap、HashTable、ConcurrentHashMap 区别?
标准答:
| 项 | HashMap | HashTable | ConcurrentHashMap |
|---|---|---|---|
| 线程安全 | 否 | 是(全表 synchronized) | 是(分段锁/CAS+synchronized) |
| null 键值 | 允许 | 不允许 | 不允许 |
| 性能 | 高 | 低(全表锁) | 高(细粒度锁) |
追问链:
- "为什么不用 HashTable?" → 全表锁并发性能极差,CHM 锁粒度细得多
- "Collections.synchronizedMap 呢?" → 也是全表锁,只是包装了一层
常见误解:❌ 以为 HashTable 和 CHM 性能差不多(CHM 并发度远高)。