前言Java 集合是 Java 基础中非常重要的一部分也是面试中的高频考点。很多同学平时写代码经常用ArrayList HashMap HashSet LinkedList ConcurrentHashMap但是面试的时候面试官往往不会只问“会不会用”而是会继续追问ArrayList 和 LinkedList 有什么区别HashMap 底层原理是什么HashMap 为什么线程不安全HashMap 为什么容量是 2 的幂ConcurrentHashMap 是怎么保证线程安全的fail-fast 是什么这篇文章整理一份 Java 集合高频八股文适合初学者和准备面试的同学快速复习。1. Java 集合框架主要分为哪几类Java 集合框架主要分为两大类Collection MapCollectionCollection 主要存储单个元素。它下面常见的子接口有List Set QueueMapMap 主要存储键值对也就是key-value常见集合类有ArrayList LinkedList HashSet TreeSet HashMap TreeMap ConcurrentHashMap简单理解Collection 存的是一个个元素Map 存的是一组组键值对。2. List、Set、Map 有什么区别类型特点常见实现List有序、可重复ArrayList、LinkedListSet无序、不可重复HashSet、TreeSetMap键值对key 不可重复HashMap、TreeMapListList 中的元素有下标可以通过索引访问。list.get(0);SetSet 中的元素不能重复。常用于去重。MapMap 以 key-value 形式存储数据。map.put(name, zhangsan);3. ArrayList 底层原理是什么ArrayList 底层是基于数组实现的。数组的特点是查询快。增删慢。内存连续。支持下标访问。例如ListString list new ArrayList(); list.add(Java); list.add(MySQL);当调用 add() 方法时ArrayList 会把元素放入底层数组中。如果数组容量不够就会触发扩容。4. ArrayList 默认容量是多少在 JDK 8 中创建空的 ArrayListListString list new ArrayList();此时底层数组还是一个空数组。当第一次添加元素时才会初始化容量为10也就是说ArrayList 是懒加载式初始化数组第一次 add 时默认容量才变成 10。5. ArrayList 如何扩容ArrayList 容量不够时会自动扩容。在 JDK 8 中扩容后的容量大约是原来的1.5 倍例如原容量是 10扩容后大约变成 15。扩容过程大致是创建一个更大的新数组。把旧数组中的元素复制到新数组。把新数组作为底层数组。所以如果提前知道数据量建议使用带容量的构造方法ListString list new ArrayList(1000);这样可以减少扩容次数提高性能。6. ArrayList 和 LinkedList 有什么区别对比项ArrayListLinkedList底层结构数组双向链表查询快慢随机访问支持不适合头尾增删较慢较快内存占用较少较多ArrayList 适合查询多、增删少的场景。LinkedList 适合头尾增删较多的场景。实际开发中大多数情况下使用 ArrayList 更多。7. LinkedList 底层原理是什么LinkedList 底层是双向链表。每个节点中包含当前元素。前一个节点引用。后一个节点引用。结构类似prev - node - next因为每个节点都保存前后引用所以 LinkedList 插入和删除节点时不需要移动大量元素。但是查询某个位置的元素时需要从头或尾开始遍历所以随机访问效率较低。8. HashMap 底层原理是什么HashMap 是面试中的重点。在 JDK 8 中HashMap 底层结构是数组 链表 红黑树当我们执行map.put(name, zhangsan);大致流程是根据 key 计算 hash 值。根据 hash 值计算数组下标。如果该位置没有元素直接插入。如果该位置有元素说明发生哈希冲突。冲突时先用链表存储。链表过长时会转换为红黑树。简单理解HashMap 通过 hash 定位数组位置如果冲突就用链表或红黑树解决。9. 什么是哈希冲突哈希冲突指的是不同的 key 经过 hash 计算后落到了数组的同一个位置。例如key1 - 下标 3 key2 - 下标 3这就发生了哈希冲突。HashMap 解决哈希冲突的方式是链表 红黑树JDK 8 中当链表长度达到一定条件时会转换为红黑树提高查询效率。10. HashMap 什么时候会转成红黑树在 JDK 8 中HashMap 链表转红黑树需要满足两个主要条件链表长度 8 数组容量 64如果链表长度达到 8但数组容量小于 64HashMap 会优先选择扩容而不是树化。这样做的原因是数据量较小时扩容通常比树化更合适。11. HashMap 的默认初始容量是多少HashMap 默认初始容量是16默认负载因子是0.75当元素数量超过容量 * 负载因子时就会触发扩容。例如默认容量 16负载因子 0.75那么阈值是16 * 0.75 12当元素数量超过 12 时就会扩容。12. HashMap 为什么容量是 2 的幂HashMap 的容量设计为 2 的幂主要是为了提高取模效率和减少哈希冲突。一般计算数组下标可以用hash % length但是取模运算性能相对较低。当 length 是 2 的幂时可以使用位运算hash (length - 1)位运算效率更高。同时容量是 2 的幂可以让数据分布更均匀减少哈希冲突。13. HashMap 的 put 流程是什么HashMap 的 put 流程大致如下判断数组是否为空如果为空则初始化。根据 key 计算 hash 值。根据 hash 值计算数组下标。如果该位置为空直接插入新节点。如果该位置不为空判断 key 是否相同。key 相同则覆盖旧值。key 不同则插入链表或红黑树。插入后判断是否需要扩容。面试中可以这样总结HashMap 先根据 key 的 hash 值定位桶位置如果桶为空就直接插入如果桶不为空就判断 key 是否已存在存在则覆盖不存在则挂到链表或红黑树中最后根据元素数量判断是否扩容。14. HashMap 的 get 流程是什么HashMap 的 get 流程大致如下根据 key 计算 hash 值。根据 hash 值计算数组下标。找到对应桶位置。判断第一个节点的 key 是否匹配。如果匹配直接返回 value。如果不匹配继续在链表或红黑树中查找。找到就返回 value找不到就返回 null。核心就是hash 定位数组下标然后在桶内查找 key15. HashMap 为什么线程不安全HashMap 是线程不安全的。原因包括多线程同时 put可能导致数据覆盖。多线程扩容时可能造成数据丢失。读写并发时可能读到不一致的数据。JDK 7 中多线程扩容甚至可能形成死循环链表。如果在多线程环境下使用 Map可以选择ConcurrentHashMap或者Collections.synchronizedMap(new HashMap());实际开发中更推荐 ConcurrentHashMap。16. HashMap 和 Hashtable 有什么区别对比项HashMapHashtable线程安全不安全安全性能较高较低null key/value允许不允许出现时间较新较老推荐程度常用不推荐Hashtable 的方法基本都使用 synchronized 修饰所以线程安全但性能较差。现在实际开发中很少使用 Hashtable。17. HashMap 和 ConcurrentHashMap 有什么区别对比项HashMapConcurrentHashMap线程安全不安全安全使用场景单线程多线程是否允许 null允许 null key 和 null value不允许 null key 和 null value性能单线程下较好并发场景下较好如果是普通单线程业务使用 HashMap。如果是多线程共享数据使用 ConcurrentHashMap。18. ConcurrentHashMap 底层原理是什么JDK 8 中ConcurrentHashMap 底层结构也是数组 链表 红黑树它主要通过CAS synchronized来保证线程安全。大致逻辑插入空桶时使用 CAS。桶中已有元素时使用 synchronized 锁住桶头节点。扩容时多个线程可以协助扩容。读取操作通常不加锁提高并发性能。相比 Hashtable 全表加锁ConcurrentHashMap 锁粒度更小性能更好。19. ConcurrentHashMap 为什么不允许 nullConcurrentHashMap 不允许 null key 和 null value。主要原因是在并发环境下null 会造成歧义。例如map.get(name)如果返回 null无法判断key 不存在。key 存在但 value 是 null。在并发情况下这种判断更容易出问题。所以 ConcurrentHashMap 直接禁止 null。20. HashSet 底层原理是什么HashSet 底层其实是 HashMap。HashSet 添加元素时本质上是把元素作为 HashMap 的 key。value 使用一个固定的 Object 对象占位。例如set.add(Java);底层类似map.put(Java, PRESENT);所以 HashSet 能去重本质上依赖的是 HashMap 的 key 不重复。21. TreeMap 和 TreeSet 有什么特点TreeMap 和 TreeSet 底层都是红黑树。特点是元素有序。查询、插入、删除时间复杂度是 O(log n)。可以按照自然顺序排序。也可以自定义比较器排序。例如TreeSetInteger set new TreeSet(); set.add(3); set.add(1); set.add(2);输出时会按照顺序1, 2, 3如果需要排序集合可以考虑 TreeMap 或 TreeSet。22. 什么是 fail-fastfail-fast 是 Java 集合中的快速失败机制。当一个线程正在遍历集合时如果另一个线程修改了集合结构就可能抛出ConcurrentModificationException例如ListString list new ArrayList(); list.add(Java); list.add(MySQL); for (String item : list) { list.remove(item); }这段代码可能会触发 fail-fast。原因是集合在遍历过程中被修改了。23. 如何避免 ConcurrentModificationException可以使用迭代器的 remove() 方法IteratorString iterator list.iterator(); while (iterator.hasNext()) { String item iterator.next(); if (Java.equals(item)) { iterator.remove(); } }或者使用removeIf例如list.removeIf(item - Java.equals(item));如果是并发场景可以使用并发集合例如CopyOnWriteArrayList ConcurrentHashMap24. 什么是 fail-safefail-safe 是安全失败机制。它通常不会直接在原集合上遍历而是遍历集合的副本。例如CopyOnWriteArrayList遍历时即使其他线程修改集合也不会抛出 ConcurrentModificationException。但是它有一个缺点遍历时看到的数据可能不是最新的。所以 fail-safe 更适合读多写少的场景。25. Iterator 和 foreach 有什么关系foreach 本质上是语法糖。例如for (String item : list) { System.out.println(item); }底层其实是使用 Iterator 进行遍历。所以在 foreach 遍历过程中直接调用集合的 remove 方法可能会触发 fail-fast。如果需要边遍历边删除建议使用 Iterator 的 remove 方法。26. CopyOnWriteArrayList 是什么CopyOnWriteArrayList 是线程安全的 List。它的核心思想是写时复制读操作不加锁。写操作时会复制一份新数组在新数组上修改修改完成后再替换旧数组。优点读操作性能高。遍历时不会抛出 ConcurrentModificationException。适合读多写少场景。缺点写操作开销大。占用更多内存。数据存在短暂不一致。常见场景配置列表 黑名单列表 监听器列表27. ArrayList 和 Vector 有什么区别对比项ArrayListVector线程安全不安全安全性能较高较低扩容约 1.5 倍默认 2 倍推荐程度常用较少使用Vector 的方法大多使用 synchronized 修饰所以线程安全但性能较差。现在实际开发中很少使用 Vector。28. Queue 和 Deque 有什么区别QueueQueue 是队列通常是先进先出。FIFO常见方法offer() poll() peek()DequeDeque 是双端队列两端都可以插入和删除。既可以当队列使用也可以当栈使用。常见实现ArrayDeque LinkedList实际开发中如果需要栈结构推荐使用ArrayDeque而不是老的 Stack。29. Java 集合如何选择可以按场景选择查询多、增删少ArrayList需要去重HashSet需要排序TreeSet TreeMap存储 key-valueHashMap多线程 MapConcurrentHashMap读多写少的线程安全 ListCopyOnWriteArrayList队列ArrayDeque LinkedList PriorityQueue30. Java 集合面试怎么回答更稳回答集合八股时不要只背定义可以按照底层结构 特点 使用场景 注意点比如问“ArrayList 和 LinkedList 有什么区别”可以这样回答ArrayList 底层是数组支持随机访问所以查询速度快但中间插入和删除时需要移动元素。LinkedList 底层是双向链表插入和删除节点比较方便但查询元素需要遍历链表所以随机访问慢。实际开发中如果没有特殊的频繁头尾增删需求一般优先使用 ArrayList。再比如问“HashMap 底层原理是什么”可以这样回答JDK 8 中 HashMap 底层是数组、链表和红黑树。put 数据时会先根据 key 计算 hash再根据 hash 计算数组下标。如果桶为空就直接插入如果发生哈希冲突就用链表存储当链表长度达到 8 并且数组容量大于等于 64 时会转换成红黑树提高查询效率。HashMap 默认容量是 16负载因子是 0.75超过阈值会进行扩容。总结Java 集合是 Java 基础面试中非常重要的一块。高频内容主要集中在集合体系 ArrayList LinkedList HashMap HashSet ConcurrentHashMap TreeMap fail-fast CopyOnWriteArrayList 集合选择其中最重要的一定是ArrayList 底层和扩容机制。ArrayList 和 LinkedList 的区别。HashMap 底层结构。HashMap 的 put 流程。HashMap 为什么线程不安全。ConcurrentHashMap 如何保证线程安全。fail-fast 是什么。如果是初学者建议先把这些问题理解清楚再去结合源码阅读。面试时不用一上来就讲得特别深但一定要能把底层结构、使用场景和注意点说清楚。