【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 是外部
相关推荐
她的男孩7 小时前
Spring Boot 接 Flowable 工作流:用 3 个注解搭一个请假审批流程
java·后端·架构
不好听6138 小时前
JavaScript 的 this 到底指向谁?
javascript·面试
烬羽8 小时前
面试官:聊聊 LocalStorage 和 this 指向?看这篇就够了
面试·程序员
weedsfly8 小时前
JS垃圾回收:从原理到项目实战,彻底根治内存泄漏
前端·javascript·面试
荣码8 小时前
LLM结构化输出:让AI返回JSON而不是废话,我踩了4个坑
java·python
plainGeekDev10 小时前
Gson → kotlinx.serialization
android·java·kotlin
小bo波19 小时前
Java Swing 图形用户界面实验 —— 从算术练习到游戏开发的完整实践
java·课程设计·gui·游戏开发·扫雷·swing
咖啡八杯20 小时前
GoF设计模式——备忘录模式
java·后端·spring·设计模式