【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 是外部
相关推荐
cfm_29141 小时前
JVM GC垃圾回收初步了解
java·开发语言·jvm
心之伊始1 小时前
LangChain4j RAG 实战:Java 后端如何把本地文档接入 Embedding 检索链路
java·架构·源码分析·csdn
许彰午2 小时前
17_synchronized关键字深度解析
java·开发语言
不懂数据的小白3 小时前
面试题一:【三】AB实验入门(验证)
面试
Xzh04233 小时前
AI Agent 学习路线(Java 后端方向)
java·人工智能·学习
我叫黑大帅3 小时前
通过php 中的Route:: 的写法了解什么是静态类调用
后端·面试·php
Aphasia3114 小时前
从输入URL到页面展示全流程
前端·面试
艾利克斯冰4 小时前
Java 设计模式-行为型模式(更新中)
java·开发语言·设计模式
倒霉蛋小马4 小时前
Java新特性:record关键字
java·开发语言