【Java】集合面试全套精讲|HashMap/ArrayList高频考点完整版

大家好,我是程序员二叉。


简介

本文整理Java面试高频集合核心考点,聚焦HashMap、ArrayList、HashSet、Tree系列、LinkedHashMap等常用容器;梳理底层结构、扩容逻辑、存取流程、哈希冲突、线程安全、排序机制、容器差异对比等重难点,区分JDK7与JDK8 HashMap关键改动,清晰对比各类容器适用场景,可直接用于面试背诵与技术笔记查阅。欢迎点赞收藏关注。


一、HashMap 基础参数 & 扩容机制

1. 默认值

  • 默认容量 :16(必须是 2 的 n 次方)
  • 默认负载因子 :0.75
  • 扩容倍数 :2 倍(newCap = oldCap << 1)

2. 扩容机制

  1. 当 元素数量 > 容量 × 负载因子 时触发扩容
  2. 扩容 = 创建新的 2 倍容量数组
  3. 重新计算所有元素的哈希位置,迁移到新数组
  4. JDK8 迁移时保持链表顺序,避免死循环

二、HashMap put () 完整执行流程(JDK8)

  1. 首次 put :数组未初始化,先延迟初始化容量为 16

  2. 计算 key 的 hash 值 ,再计算数组下标 i = (n - 1) & hash

  3. 若该位置无元素,直接插入

  4. 若该位置是链表:

    • 遍历链表,equals 对比 key
    • 找到则覆盖 value
    • 没找到则尾插法插入
    • 链表长度 ≥ 8 且数组容量 ≥ 64 → 转为红黑树
  5. 若该位置是红黑树:按树结构插入或更新

  6. 插入完成后判断是否需要扩容,需要则扩容


三、HashMap get () 完整执行流程

  1. 计算 key 的 hash,定位数组下标
  2. 数组位置无节点 → 返回 null
  3. 第一个节点 key 匹配 → 直接返回 value
  4. 若是红黑树 → 树查找
  5. 若是链表 → 遍历链表 equals 匹配
  6. 找不到 → 返回 null

四、HashMap 如何解决哈希冲突?

  • JDK7:数组 + 链表 + 头插法
  • JDK8:数组 + 链表 + 红黑树 + 尾插法

冲突解决策略:

  1. 相同下标时用链表存储
  2. 链表过长会影响查询,JDK8 升级为红黑树
  3. 红黑树节点变少后会退化为链表

五、HashMap 为什么线程不安全?

1. JDK7:头插法 → 死循环

扩容时链表逆序,多线程下形成环形链表 ,get() 时无限循环。

2. JDK8:尾插法解决死循环,但仍不安全

  • 多线程同时 put 会数据覆盖
  • size 计数不准确
  • 扩容期间数据丢失

结论 :HashMap 无锁设计,任何多线程场景都不能用。


六、头插法 vs 尾插法 缺点

1. 头插法(JDK7)

  • 优点:插入快
  • 致命缺点 :多线程扩容 死循环

2. 尾插法(JDK8)

  • 优点:无死循环
  • 缺点:依然线程不安全,会出现数据覆盖

七、HashMap 和 Hashtable 区别

  1. 线程安全:HashMap 不安全;Hashtable 安全(全方法 synchronized)
  2. null 值:HashMap 允许 1 个 null key + 多个 null value;Hashtable 不允许 null
  3. 效率:HashMap 快;Hashtable 极慢
  4. 扩容:HashMap 2 倍;Hashtable 2 倍 +1
  5. 底层结构:HashMap JDK8 有红黑树;Hashtable 永远链表
  6. 推荐 :不用 Hashtable,多线程用 ConcurrentHashMap

八、负载因子为什么是 0.75?

官方选择 空间成本 + 查询效率 的最优平衡:

  • <0.75:空间浪费,冲突少
  • >0.75:冲突剧增,链表变长,查询变慢
  • 0.75 是数学统计最优值

九、链表转红黑树阈值 8 / 6 原理

1. 树化阈值:8

根据泊松分布,哈希冲突达到 8 的概率极低 (千万分之一)

正常情况下几乎不会树化,避免红黑树浪费内存。

2. 退化为链表:6

不使用 7 是为了防止频繁在树和链表之间反复切换(抖动)。


十、为什么不直接用红黑树?

  1. 红黑树内存占用是链表的 2 倍以上
  2. 链表长度短的时候,查询速度不比树慢
  3. 只有冲突严重时才需要树来优化 O (n) → O (logn)

设计思想:用最小内存满足绝大多数场景。


十一、ArrayList vs LinkedList 底层 & 性能

1. 底层

  • ArrayList:动态数组
  • LinkedList:双向链表

2. 性能对比

  • 查询:ArrayList 快(O (1));LinkedList 慢(O (n))
  • 尾部增删:ArrayList 快;LinkedList 一般
  • 中间 / 头部增删:ArrayList 慢(拷贝数组);LinkedList 快(O (1))
  • 内存:ArrayList 紧凑;LinkedList 节点开销大

3. 开发建议

99% 场景用 ArrayList,LinkedList 极少使用。


十二、ArrayList 扩容机制

  1. 默认容量:0(首次添加才扩容为 10)
  2. 扩容条件:元素数量达到数组长度
  3. 新容量 = 旧容量 × 1.5
  4. 底层使用 Arrays.copyOf 复制数组

十三、HashSet 如何保证不重复?底层是什么?

1. 底层

基于 HashMap 实现 ,value 存固定的 new Object()。

2. 不重复原理

  • add 元素 = map.put (e, PRESENT)
  • key 唯一= 元素唯一
  • 判断重复:hashCode() + equals()
    • hashCode 不同 → 一定不重复
    • hashCode 相同 → 再用 equals 确认

十四、HashMap vs TreeMap

  1. 有序性 :HashMap 无序;TreeMap 有序(自然排序 / 定制排序)
  2. 底层 :HashMap 数组 + 链表 + 红黑树;TreeMap 红黑树
  3. key 要求:HashMap key 可 null;TreeMap key 不能 null
  4. 性能:HashMap 增删查更快;TreeMap 适合排序场景

十五、LinkedHashMap 有序性如何实现?

内部维护 双向链表

  • 插入元素时同步维护链表前后指向
  • 遍历按 插入顺序 输出
  • 支持 访问顺序(LRU 缓存)

十六、TreeSet 排序原理 + Comparable vs Comparator

1. TreeSet 排序原理

底层 TreeMap,基于 红黑树 自动排序。

2. Comparable(内部比较器)

  • 类 自身实现 implements Comparable
  • 重写 compareTo()
  • 定义默认排序规则

3. Comparator(外部比较器)

  • 单独编写比较器类
  • 重写 compare()
  • 不修改原类,灵活定制排序

4. 区别

  • Comparable:自身比较,一个规则
  • Comparator:外部比较,多个规则,解耦

总结(面试必背)

  1. HashMap 默认容量 16、负载因子 0.75、扩容 2 倍
  2. JDK8 结构:数组 + 链表 + 红黑树,尾插法,无线程死循环
  3. 线程不安全原因:JDK7 死循环、JDK8 数据覆盖
  4. HashSet 底层就是 HashMap,靠 key 保证唯一
  5. ArrayList 数组、LinkedList 链表,优先用 ArrayList
  6. Tree 系列靠红黑树排序,Comparable 是自身,Comparator 是外部
相关推荐
小羊没烦恼!3 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
俊昭喜喜里3 天前
java中的继承和多态的区别
java
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时3 天前
01-06-A-JVM排查实战详解
java·jvm
晨米酱3 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
vipxieliang3 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot
ba_pi3 天前
mysql 查询所有表名并授权
java