Java 集合面试题 52 道

笔记/Java/面试专栏/Java 集合面试题 52 道

整理自 Java 集合面试题 52 道,已精简冗余表述,保留考点与结论。详细原理见下方「相关学习笔记」或各题末尾链接。

相关学习笔记#

专题笔记
集合框架集合框架总览HashMap · CHM
泛型Java 泛型
Object 类Java Object 类
数组Java 数组基础
字符串Java 字符串

目录#


集合概述#

1. 什么是集合#

对象引用的容器(不是对象本身)。主要三类:Set(集)、List(列表)、Map(映射)。→ 集合框架总览

2. 集合的特点#

集中管理对象;长度可变(对比数组需提前定长)。

3. 集合和数组的区别#

维度数组集合
长度固定可变
存储类型基本类型 + 引用类型仅引用类型(基本类型用包装类)
元素类型同一类型可不同(泛型约束除外)

4. 使用集合框架的好处#

容量自增长;高性能数据结构与算法;易扩展改写;降低维护成本。

5. 常用的集合类有哪些#

  • Collection 子接口SetListQueue
  • Set 实现HashSetTreeSetLinkedHashSet
  • List 实现ArrayListLinkedListVectorStack
  • Map 实现HashMapTreeMapHashtableConcurrentHashMapProperties

6. List、Set、Map 三者的区别#

Collection 体系List/Set/Queue)存单个元素;Map 存键值对,不继承 Collection

  • List:有序、可重复、可多个 null、有索引。实现:ArrayListLinkedListVector
  • Set:无序(或按规则排序)、不可重复、最多一个 null。实现:HashSetLinkedHashSetTreeSet
  • Map:键唯一、值可重复;键无序(TreeMap 除外)。实现:HashMapLinkedHashMapTreeMapConcurrentHashMap

7. 集合框架底层数据结构#

类型实现底层
ListArrayList / VectorObject 数组
ListLinkedList双向链表
SetHashSet基于 HashMap
SetLinkedHashSet基于 LinkedHashMap
SetTreeSet红黑树
MapHashMap数组 + 链表 / 红黑树(JDK 8+)
MapLinkedHashMapHashMap + 双向链表
MapHashtable数组 + 链表
MapTreeMap红黑树

8. 哪些集合类是线程安全的#

  • Vector:方法 synchronized,已不推荐
  • Hashtable:全表锁,已不推荐
  • ConcurrentHashMap:分段锁(1.7)/ CAS + synchronized 桶头(1.8),推荐

ArrayListHashMapHashSet 等均非线程安全

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 有什么区别#

IteratorListIterator
范围Set + List仅 List
方向单向双向
额外能力removeadd、set、previous、索引

15. 遍历 List 有哪些方式?最佳实践#

  1. for 循环 + 下标:基于计数器
  2. Iterator:统一接口,可删除
  3. 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 的区别#

维度ArrayListLinkedList
结构动态数组双向链表
随机访问慢(需遍历)
头尾插入删除尾部快,中间慢头尾快
内存较省每节点多两个指针
线程安全

结论:大多数场景优先 ArrayList;频繁头尾增删且少随机访问时用 LinkedList

19. ArrayList 和 Vector 的区别#

均实现 List,底层都是数组。

维度ArrayListVector
线程安全是(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 规定

  1. equals 相等 → hashCode 必同
  2. hashCode 同 → equals 不一定相等
  3. 重写 equals 必须重写 hashCode

== vs equals== 比地址;equals 比内容。→ Java Object 类

26. HashSet 与 HashMap 的区别#

HashMapHashSet
接口MapSet
存储键值对仅对象(作 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 流程概要

  1. 算 key 的 hash,定位数组下标
  2. 桶空 → 直接放;桶非空 → 比 hash + equals
  3. key 相同 → 覆盖 value;不同 → 挂链表或红黑树
  4. 链表长度 > 8 且数组 ≥ 64 → 树化

get:算 hash 找桶 → 链表/树中比 key。

30. HashMap 在 JDK 1.7 和 1.8 中有哪些不同#

维度JDK 1.7JDK 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 方法具体流程#

  1. table 空 → resize 初始化
  2. i = (n-1) & hash 定位桶
  3. 桶空 → newNode 放入
  4. 桶非空:首节点 key 相同 → 覆盖;是 TreeNode → 树插入;否则链表尾插
  5. 链表 ≥ 8 → treeifyBin
  6. ++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#

可以,但需:

  1. 重写 equals 则必须重写 hashCode
  2. 遵守 equals/hashCode 契约
  3. 最佳实践: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 容量远小于此,直接取余/映射会碰撞严重且可能越界。

解决

  1. 扰动函数压缩并混合高低位
  2. 容量为 2 的幂,用 (n-1) & hash 代替 %,等价且更快

39. HashMap 的长度为什么是 2 的幂次方#

(n-1) & hash 等价于 hash % n(当 n 为 2 的幂);位运算更快;配合扰动使分布更均匀。创建时容量会向上取整到 2 的幂


Map 接口 · 选型与对比#

40. HashMap 与 Hashtable 有什么区别#

维度HashMapHashtable
线程安全是(synchronized)
null允许 null 键/值不允许
初始容量1611
扩容2 倍2n+1
结构1.8 有红黑树数组+链表
推荐单线程首选已淘汰,用 ConcurrentHashMap

41. 什么是 TreeMap#

基于红黑树的有序 key-value 集合;按键的自然顺序或构造时传入的 Comparator 排序;非线程安全。

42. 如何决定使用 HashMap 还是 TreeMap#

增删查定位 → HashMap(O(1) 均摊);需要有序遍历 keyTreeMap(O(log n))。

43. HashMap 和 ConcurrentHashMap 的区别#

维度HashMapConcurrentHashMap
线程安全
null允许不允许 null 键/值
锁粒度1.7 Segment;1.8 桶头 synchronized / CAS

ConcurrentHashMap#

44. ConcurrentHashMap 和 Hashtable 的区别#

维度ConcurrentHashMapHashtable
锁粒度分段 / 桶级整张表
并发度低(同一时刻仅一个线程)
结构1.8 同 HashMap数组+链表
推荐

45. ConcurrentHashMap 底层实现原理#

JDK 1.7Segment 数组 + HashEntry 链表;Segment 继承 ReentrantLock,锁一段数据。

JDK 1.8:废弃 Segment,Node 数组 + 链表/红黑树;空桶 CAS 插入;非空则 synchronized 锁桶头节点(或 TreeBin),粒度更细。

put 概要:桶空 → CAS 放入;桶非空 → synchronized 锁头,链表/树插入;必要时树化;更新 baseCount。


辅助工具类#

46. Array 和 ArrayList 有何区别#

维度ArrayArrayList
存储基本类型 + 对象仅对象(自动装箱)
大小固定自动扩展
API丰富(addAll、removeAll 等)

基本类型固定大小场景用数组;动态对象集合用 ArrayList。

47. 如何实现 Array 和 List 之间的转换#

同第 17 题:Arrays.asList(array)list.toArray()

48. Comparable 和 Comparator 的区别#

ComparableComparator
java.langjava.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(不要求元素本身可比较)
文章目录

文章目录