整理自 Java 集合面试题 52 道,已精简冗余表述,保留考点与结论。详细原理见下方「相关学习笔记」或各题末尾链接。
相关学习笔记
| 专题 | 笔记 |
|---|---|
| 集合框架 | 集合框架总览(HashMap · CHM) |
| 泛型 | Java 泛型 |
| Object 类 | Java Object 类 |
| 数组 | Java 数组基础 |
| 字符串 | Java 字符串 |
目录
集合概述
1. 什么是集合
放对象引用的容器(不是对象本身)。主要三类:Set(集)、List(列表)、Map(映射)。→ 集合框架总览
2. 集合的特点
集中管理对象;长度可变(对比数组需提前定长)。
3. 集合和数组的区别
| 维度 | 数组 | 集合 |
|---|---|---|
| 长度 | 固定 | 可变 |
| 存储类型 | 基本类型 + 引用类型 | 仅引用类型(基本类型用包装类) |
| 元素类型 | 同一类型 | 可不同(泛型约束除外) |
4. 使用集合框架的好处
容量自增长;高性能数据结构与算法;易扩展改写;降低维护成本。
5. 常用的集合类有哪些
- Collection 子接口:
Set、List、Queue - Set 实现:
HashSet、TreeSet、LinkedHashSet - List 实现:
ArrayList、LinkedList、Vector、Stack - Map 实现:
HashMap、TreeMap、Hashtable、ConcurrentHashMap、Properties
6. List、Set、Map 三者的区别
Collection 体系(List/Set/Queue)存单个元素;Map 存键值对,不继承 Collection。
- List:有序、可重复、可多个
null、有索引。实现:ArrayList、LinkedList、Vector - Set:无序(或按规则排序)、不可重复、最多一个
null。实现:HashSet、LinkedHashSet、TreeSet - Map:键唯一、值可重复;键无序(
TreeMap除外)。实现:HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap
7. 集合框架底层数据结构
| 类型 | 实现 | 底层 |
|---|---|---|
| List | ArrayList / Vector | Object 数组 |
| List | LinkedList | 双向链表 |
| Set | HashSet | 基于 HashMap |
| Set | LinkedHashSet | 基于 LinkedHashMap |
| Set | TreeSet | 红黑树 |
| Map | HashMap | 数组 + 链表 / 红黑树(JDK 8+) |
| Map | LinkedHashMap | HashMap + 双向链表 |
| Map | Hashtable | 数组 + 链表 |
| Map | TreeMap | 红黑树 |
8. 哪些集合类是线程安全的
- Vector:方法
synchronized,已不推荐 - Hashtable:全表锁,已不推荐
- ConcurrentHashMap:分段锁(1.7)/ CAS + synchronized 桶头(1.8),推荐
ArrayList、HashMap、HashSet 等均非线程安全。
9. Java 集合的快速失败机制 fail-fast
多线程(或单线程错误方式)在迭代期间修改集合结构,迭代器检测到 modCount 变化,抛 ConcurrentModificationException。
原因:迭代器维护 expectedModCount,与集合 modCount 不一致即失败。
解决:结构修改加锁;或使用 CopyOnWriteArrayList 等 fail-safe 容器。
10. 怎么确保一个集合不能被修改
Collections.unmodifiableCollection(c) 创建只读视图,任何修改操作抛 UnsupportedOperationException。
Collection 接口 · List
11. Iterator 是什么
遍历 Collection 的统一接口,取代旧 Enumeration;允许在迭代中通过 remove() 删除元素。
12. Iterator 怎么使用?有什么特点
Iterator it = list.iterator() → hasNext() / next()。特点:单向遍历;结构被外部修改时抛异常,相对更安全。
13. 如何边遍历边移除 Collection 中的元素
正确:Iterator.remove()。
错误:增强 for 中 list.remove(i) → ConcurrentModificationException(foreach 底层也是 Iterator,与外部 remove 冲突)。
14. Iterator 和 ListIterator 有什么区别
| Iterator | ListIterator | |
|---|---|---|
| 范围 | Set + List | 仅 List |
| 方向 | 单向 | 双向 |
| 额外能力 | remove | add、set、previous、索引 |
15. 遍历 List 有哪些方式?最佳实践
- for 循环 + 下标:基于计数器
- Iterator:统一接口,可删除
- foreach:语法糖,底层 Iterator,不能删改
最佳实践:实现 RandomAccess 的 List(如 ArrayList)用 for 循环;否则用 Iterator / foreach。LinkedList 未实现 RandomAccess。
16. ArrayList 的优缺点
优点:数组实现 + RandomAccess,随机访问 O(1);尾部追加方便。
缺点:中间插入/删除需元素复制,代价高。
适合:顺序追加 + 随机访问多的场景。
17. 如何实现数组和 List 之间的转换
- 数组 → List:
Arrays.asList(array)(返回固定大小列表,不能增删) - List → 数组:
list.toArray()或list.toArray(new String[0])
18. ArrayList 和 LinkedList 的区别
| 维度 | ArrayList | LinkedList |
|---|---|---|
| 结构 | 动态数组 | 双向链表 |
| 随机访问 | 快 | 慢(需遍历) |
| 头尾插入删除 | 尾部快,中间慢 | 头尾快 |
| 内存 | 较省 | 每节点多两个指针 |
| 线程安全 | 否 | 否 |
结论:大多数场景优先 ArrayList;频繁头尾增删且少随机访问时用 LinkedList。
19. ArrayList 和 Vector 的区别
均实现 List,底层都是数组。
| 维度 | ArrayList | Vector |
|---|---|---|
| 线程安全 | 否 | 是(synchronized) |
| 性能 | 更高 | 较低 |
| 扩容 | 1.5 倍 | 2 倍(或 2n+1) |
单线程优先 ArrayList。
20. 插入数据时 ArrayList、LinkedList、Vector 谁较快
- ArrayList / Vector:数组存储,按下标访问快;中间插入需移动元素
- Vector:方法同步,比 ArrayList 慢
- LinkedList:插入只需改指针,中间插入通常更快
21. 多线程场景下如何使用 ArrayList
Collections.synchronizedList(list) 包装成线程安全列表;或改用 CopyOnWriteArrayList(读多写少)。
22. 为什么 ArrayList 的 elementData 加 transient
ArrayList 实现 Serializable,但 elementData 数组可能有很多空位。transient 阻止默认序列化数组,在 writeObject 中只序列化 size 以内的有效元素,减小体积、加快速度。
23. List 和 Set 的区别
均继承 Collection。
- List:有序、可重复、多 null、有索引,可 for 下标遍历
- Set:无序(或排序)、不可重复、最多一个 null,只能 Iterator 遍历
效率特点:Set 检索慢、增删快(不移动大量元素);List 查找快(数组类)、中间增删慢。
Set 接口
24. HashSet 的实现原理
基于 HashMap:元素存为 HashMap 的 key,value 统一为 PRESENT 占位对象。操作本质调用 HashMap 的 put。
25. HashSet 如何检查重复
add() → HashMap.put():先比 hashCode 定位桶,再比 equals。key 相等则覆盖(返回旧 value),Set 视角即重复不加入。
hashCode 与 equals 规定:
- equals 相等 → hashCode 必同
- hashCode 同 → equals 不一定相等
- 重写 equals 必须重写 hashCode
== vs equals:== 比地址;equals 比内容。→ Java Object 类
26. HashSet 与 HashMap 的区别
| HashMap | HashSet | |
|---|---|---|
| 接口 | Map | Set |
| 存储 | 键值对 | 仅对象(作 key) |
| 添加 | put(k,v) | add(e) |
| null | 允许 null 键 | 允许一个 null |
| 速度 | 较快(唯一键) | 稍慢 |
Map 接口 · HashMap 原理
27. 什么是 Hash 算法
把任意长度二进制映射为固定长度的较小值(哈希值)。
28. 什么是链表
物理地址不连续的节点通过指针连接。单链表只有 next;双向链表有 pre + next。优点:插入删除快、内存灵活;缺点:不能随机访问。
29. HashMap 的实现原理
基于哈希表的 Map 非同步实现;允许 null 键和 null 值;不保证顺序。
结构:数组 + 链表(+ 红黑树,JDK 8+)。
put 流程概要:
- 算 key 的 hash,定位数组下标
- 桶空 → 直接放;桶非空 → 比 hash + equals
- key 相同 → 覆盖 value;不同 → 挂链表或红黑树
- 链表长度 > 8 且数组 ≥ 64 → 树化
get:算 hash 找桶 → 链表/树中比 key。
30. HashMap 在 JDK 1.7 和 1.8 中有哪些不同
| 维度 | JDK 1.7 | JDK 1.8 |
|---|---|---|
| 结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
| 初始化 | inflateTable() | 集成到 resize() |
| hash 扰动 | 4 次位运算 + 5 次异或 | 1 次位运算 + 1 次异或 |
| 冲突处理 | 链表 | 链表 / 树化(>8) |
| 插入 | 头插法 | 尾插法 |
| 扩容位置 | 重新 hash | (e.hash & oldCap)==0 原位置,否则原位置+旧容量 |
| 多线程 | 可能死循环(头插) | 仍非线程安全,可能丢数据 |
31. 什么是红黑树
自平衡二叉查找树:节点红/黑着色 + 五条约束(根黑、叶黑、红节点子必黑、任意路径黑节点数相同等)。HashMap 在链表过长时树化,查找从 O(n) 降到 O(log n)。
32. HashMap 的 put 方法具体流程
- table 空 → resize 初始化
i = (n-1) & hash定位桶- 桶空 → newNode 放入
- 桶非空:首节点 key 相同 → 覆盖;是 TreeNode → 树插入;否则链表尾插
- 链表 ≥ 8 → treeifyBin
- ++size > threshold → resize
hash 函数:(h = key.hashCode()) ^ (h >>> 16),高低位异或减少碰撞。
33. HashMap 的扩容操作
- 触发:初始化或 size > threshold(容量 × 0.75)
- 每次扩容为 2 倍
- JDK 8:元素要么留原索引,要么移到 原索引 + oldCap(
(e.hash & oldCap) == 0判断),无需重新完整 hash
默认初始容量 16,负载因子 0.75。
34. HashMap 是怎么解决哈希冲突的
链地址法(拉链法):同 hash 桶的元素串成链表;JDK 8 链表过长转红黑树。
hash 扰动:(hashCode) ^ (hashCode >>> 16),让高位参与运算,减少低位碰撞。
开放地址法 Java HashMap 不用。
35. 能否使用任何类作为 Map 的 key
可以,但需:
- 重写
equals则必须重写hashCode - 遵守 equals/hashCode 契约
- 最佳实践:key 类不可变(如 String、Integer),避免 hash 值变化
36. 为什么 String、Integer 适合作为 HashMap 的 key
final 不可变;已重写 equals/hashCode;String 还可缓存 hashCode,减少重复计算。→ Java 字符串
37. 如果用 Object 作为 HashMap 的 Key 怎么办
必须正确重写 hashCode() 和 equals();hashCode 不要排除关键字段;equals 满足自反、对称、传递、一致,且 x.equals(null) 为 false。
38. HashMap 为什么不直接用 hashCode 作为 table 下标
hashCode 是 int(约 40 亿空间),而 table 容量远小于此,直接取余/映射会碰撞严重且可能越界。
解决:
- 扰动函数压缩并混合高低位
- 容量为 2 的幂,用
(n-1) & hash代替%,等价且更快
39. HashMap 的长度为什么是 2 的幂次方
(n-1) & hash 等价于 hash % n(当 n 为 2 的幂);位运算更快;配合扰动使分布更均匀。创建时容量会向上取整到 2 的幂。
Map 接口 · 选型与对比
40. HashMap 与 Hashtable 有什么区别
| 维度 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 否 | 是(synchronized) |
| null | 允许 null 键/值 | 不允许 |
| 初始容量 | 16 | 11 |
| 扩容 | 2 倍 | 2n+1 |
| 结构 | 1.8 有红黑树 | 数组+链表 |
| 推荐 | 单线程首选 | 已淘汰,用 ConcurrentHashMap |
41. 什么是 TreeMap
基于红黑树的有序 key-value 集合;按键的自然顺序或构造时传入的 Comparator 排序;非线程安全。
42. 如何决定使用 HashMap 还是 TreeMap
增删查定位 → HashMap(O(1) 均摊);需要有序遍历 key → TreeMap(O(log n))。
43. HashMap 和 ConcurrentHashMap 的区别
| 维度 | HashMap | ConcurrentHashMap |
|---|---|---|
| 线程安全 | 否 | 是 |
| null | 允许 | 不允许 null 键/值 |
| 锁粒度 | 无 | 1.7 Segment;1.8 桶头 synchronized / CAS |
ConcurrentHashMap
44. ConcurrentHashMap 和 Hashtable 的区别
| 维度 | ConcurrentHashMap | Hashtable |
|---|---|---|
| 锁粒度 | 分段 / 桶级 | 整张表 |
| 并发度 | 高 | 低(同一时刻仅一个线程) |
| 结构 | 1.8 同 HashMap | 数组+链表 |
| 推荐 | 是 | 否 |
45. ConcurrentHashMap 底层实现原理
JDK 1.7:Segment 数组 + HashEntry 链表;Segment 继承 ReentrantLock,锁一段数据。
JDK 1.8:废弃 Segment,Node 数组 + 链表/红黑树;空桶 CAS 插入;非空则 synchronized 锁桶头节点(或 TreeBin),粒度更细。
put 概要:桶空 → CAS 放入;桶非空 → synchronized 锁头,链表/树插入;必要时树化;更新 baseCount。
辅助工具类
46. Array 和 ArrayList 有何区别
| 维度 | Array | ArrayList |
|---|---|---|
| 存储 | 基本类型 + 对象 | 仅对象(自动装箱) |
| 大小 | 固定 | 自动扩展 |
| API | 少 | 丰富(addAll、removeAll 等) |
基本类型固定大小场景用数组;动态对象集合用 ArrayList。
47. 如何实现 Array 和 List 之间的转换
同第 17 题:Arrays.asList(array);list.toArray()。
48. Comparable 和 Comparator 的区别
| Comparable | Comparator | |
|---|---|---|
| 包 | java.lang | java.util |
| 方法 | compareTo(obj) | compare(o1, o2) |
| 含义 | 自然排序(类自身实现) | 外部比较器 |
一个类只能一种自然排序;多种排序规则用多个 Comparator + Collections.sort(list, comparator)。
49. Collection 和 Collections 有什么区别
- Collection:集合接口,
List/Set的父接口 - Collections:工具类,提供 sort、binarySearch、reverse、synchronizedXxx、unmodifiableXxx 等静态方法
→ 集合框架总览
50. TreeMap、TreeSet 和 Collections.sort 如何比较元素
- TreeSet:元素类实现
Comparable,插入时compareTo - TreeMap:键实现
Comparable(或构造时传 Comparator) - Collections.sort:
- 单参数:元素实现
Comparable - 双参数:传入
Comparator(不要求元素本身可比较)
- 单参数:元素实现