Java 集合底层深度剖析:Map 与 Set、二叉搜索树、哈希表全解

Java 集合底层深度剖析:Map 与 Set、二叉搜索树、哈希表全解

前言

日常开发中我们几乎天天在用 HashMapTreeMapHashSetTreeSet,但很多人只停留在「会调用 API」的层面:

  • 为什么 TreeMap 有序,HashMap 无序?
  • 哈希冲突到底是什么,Java 是怎么解决的?
  • 二叉搜索树为什么会退化成链表,红黑树又起到什么作用?
  • SetMap 底层有什么关联?

本文结合底层数据结构,从二叉搜索树哈希表,完整拆解 Map/Set 的底层原理、性能差异与使用场景,一次性打通集合底层逻辑。

一、底层基石:二叉搜索树(BST)

1. 二叉搜索树定义

二叉搜索树(二叉排序树)满足三大核心规则:

  1. 左子树所有节点值 < 根节点值;
  2. 右子树所有节点值 > 根节点值;
  3. 左右子树同样是二叉搜索树。

示例数组 {5,3,4,1,7,8,2,6,0,9} 构建出上图搜索树,中序遍历会得到有序序列:0,1,2,3,4,5,6,7,8,9,这也是 TreeMap/TreeSet 能实现 Key 有序的根本原因。

2. 三大核心操作

(1)查找

从根节点开始对比:

  • 相等:找到目标节点;
  • 目标更小:去左子树查找;
  • 目标更大:去右子树查找;
  • 走到 null:不存在该元素。
(2)插入
  1. 树为空,直接作为根节点;
  2. 树不为空,按照查找逻辑找到待插入的父节点;
  3. 比父节点小挂左孩子,大则挂右孩子;
  4. 存在重复 key 直接插入失败(Set/Map 不允许重复键)。
(3)删除(难点)

分三种场景处理待删除节点 cur

  1. 左孩子为空:直接用右孩子顶替 cur;
  2. 右孩子为空:直接用左孩子顶替 cur;
  3. 左右孩子都存在 :取右子树最小节点替换当前节点值,再删除该最小节点(替换法,保证二叉搜索树性质不变)。

3. 致命缺陷:性能两极分化

二叉搜索树的效率完全依赖树的高度:

  • 最优:数据随机插入,近似完全二叉树,增删查复杂度 O(logN)
  • 最差:有序插入数据,树退化成单链表,复杂度退化到 O(N),和数组遍历无区别。

为了解决退化问题,平衡二叉树(红黑树)应运而生,Java 中 TreeMap/TreeSet 底层就是红黑树(平衡版二叉搜索树),保证无论插入顺序如何,操作稳定 O(logN)

二、Map & Set:两种搜索模型

1. 两种搜索模型

  1. 纯 Key 模型(Set):只存储唯一关键字,用于去重、判断元素是否存在,例如判断单词是否在词典;
  2. Key-Value 键值模型(Map):关键字映射对应数值,例如「姓名-手机号」「单词-出现次数」。

底层关系:Set 本质是包装后的 MapTreeSet/HashSet 内部持有一个 Map,存入的元素全部作为 Map 的 Key,Value 固定填充一个空 Object。

2. Map 核心知识点

(1)接口特性
  • Map 不继承 Collection,独立接口;
  • Key 唯一不可重复,Value 允许重复;
  • 内部静态内部类 Map.Entry<K,V>:封装单条键值对,提供 getKey()getValue() 修改 Value(Key 不可修改)。
(2)常用 API 分类
  1. 增改:put(K,V) 存在则覆盖旧值,返回旧 value;
  2. 查询:get(key)getOrDefault(key, 默认值)
  3. 遍历:keySet() 获取所有键、values() 获取所有值、entrySet() 获取全部键值对;
  4. 判断:containsKey()(效率高)、containsValue()(遍历全部,O(N));
  5. 删除:remove(key)
(3)TreeMap VS HashMap 核心对比
特性 TreeMap HashMap
底层结构 红黑树(平衡二叉搜索树) 哈希桶(数组+链表/红黑树)
时间复杂度 O(logN) 平均 O(1)
有序性 Key 自然有序 无序
Null 支持 Key 不能为 null,Value 可 null Key/Value 均可为 null
对象要求 Key 实现 Comparable 或传入 Comparator 自定义 Key 必须重写 hashCode() + equals()
使用场景 需要按键排序、区间查找 追求极致查询性能,不关心顺序

3. Set 核心知识点

  1. 继承 Collection,只存储唯一 Key,天然去重;
  2. 实现类:TreeSet(红黑树,有序)、HashSet(哈希表,无序)、LinkedHashSet(哈希+双向链表,保留插入顺序);
  3. 关键限制:TreeSet 不能存 null,HashSet 允许存一个 null;
  4. 核心方法:add() 重复返回 false、contains() 判断存在、remove() 删除元素。

三、哈希表:理想 O(1) 搜索结构

二叉搜索树存在性能波动,哈希表则实现了理论一次寻址找到元素 ,是 HashMap/HashSet 的底层。

1. 哈希表核心思想

通过哈希函数建立关键字与数组下标的映射关系:

  • 插入:key 经过哈希函数算出数组下标,存入对应位置;
  • 查询:相同哈希函数计算下标,直接定位元素;
    理想状态无需任何比较,时间复杂度 O(1)

示例哈希函数 hash(key) = key % 数组容量,容量为10时,数字 1、4、5、6、7、9 会分别存入下标对应位置。

2. 哈希冲突(哈希碰撞)

不同 key 算出相同哈希下标,就是哈希冲突,例如 4 % 10 = 444 % 10 = 4

冲突无法完全避免,只能通过合理设计哈希函数、控制负载因子降低冲突概率。

3. 冲突优化手段

(1)设计优质哈希函数

常用工业级方案:

  • 除留余数法:取质数作为取模除数,分布更均匀;
  • 直接定址法:适合连续小范围数字;
  • 平方取中法、折叠法:处理长字符串、手机号等长关键字。
(2)负载因子(核心重点)

公式:负载因子 α = 表中元素个数 / 数组长度

  • α 越大,数组越满,冲突概率指数上升;
  • Java 哈希表默认阈值 0.75,超过阈值自动扩容数组为原来2倍,降低负载因子减少冲突。

4. 两种冲突解决方案

方案1:闭散列(开放地址法)

冲突时向后寻找空位存放元素,分为线性探测二次探测

  • 线性探测:冲突下标依次+1找空位,缺陷是数据扎堆聚集;
  • 二次探测:(原始下标 ± i²) % 容量,缓解聚集问题;
  • 致命缺点:不能直接删除元素,需伪删除标记,扩容开销大,Java 未采用。
方案2:开散列(哈希桶/链地址法,Java 采用)

数组每个下标对应一条单向链表,冲突元素挂载在同下标链表尾部:

  1. 寻址到数组下标;
  2. 遍历链表,通过 equals() 匹配 key;
  3. 链表过长(阈值8)自动转为红黑树,缩短遍历时间;
  4. 扩容后重新计算所有元素哈希下标,迁移到新数组。

5. 哈希桶简易实现核心逻辑

  1. 底层数组+链表节点存储 key-value;
  2. put 流程:计算哈希下标 → 遍历链表判断重复key,重复则覆盖value,无重复头插新节点;
  3. 插入后校验负载因子 ≥0.75,执行二倍扩容;
  4. get 流程:计算下标,遍历链表匹配 key 返回 value,无匹配返回默认值;

6. Java 开发必记规范

自定义类作为 HashMap/HashSet 的 Key 时,必须同时重写 hashCode()equals()

  1. hashCode():确定数组下标,相等对象哈希值必须相同;
  2. equals():链表中精准匹配对象,区分哈希值相同但实际不同的对象;
    若只重写其中一个,会出现「对象内容相等但集合判定为两个不同元素」的 bug。

四、Tree 系列 vs Hash 系列集合选型总结

  1. 需要有序、区间查询 :选 TreeMap/TreeSet,底层红黑树,稳定 O(logN)
  2. 追求最高读写性能,不关心顺序 :选 HashMap/HashSet,平均 O(1)
  3. 需要保留元素插入顺序LinkedHashMap / LinkedHashSet
  4. 去重场景:统一使用 Set;键值映射场景使用 Map;
  5. 数据量大时,哈希表性能远优于二叉搜索树;数据有序遍历需求优先红黑树集合。

五、结语

Map 和 Set 是 Java 开发的高频容器,底层分别依托平衡二叉搜索树(红黑树)哈希表两套数据结构。二叉搜索树解决有序查找问题,但存在退化风险;哈希表实现极致读写速度,但需要处理哈希冲突。

理解底层原理后,不再是只会调用 API 的开发者:遇到有序场景、大数据量查询、自定义对象作为 Key 等场景时,能精准选择合适集合,规避哈希冲突、空指针、去重失效等常见线上 Bug。

相关推荐
cyforkk1 小时前
并发控制与状态安全:Single-Flight 与幂等性的本质区别
java·安全·spring
豆角焖肉2 小时前
MyBatis延迟加载、缓存机制与注解开发
java·spring·mybatis
Hommy882 小时前
【剪映小助手】字符串列表转对象接口(Str List To Obds)
数据结构·list·剪映小助手·视频剪辑自动化·剪映api·开源剪映小助手
所愿ღ2 小时前
SSM框架-Spring3
java·开发语言·笔记·spring
java1234_小锋2 小时前
【免费】基于Spark实时交通流量分析与拥堵预测系统(Java版本+可视化大屏+Kafka+SpringBoot+Vue3) 锋哥原创出品,必属精品
java·大数据·spark·kafka·实时交通流量分析与拥堵预测
组合缺一2 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·服务器·网络协议·http·solon
LSL666_2 小时前
SpringBoot静态资源映射
java·spring boot·spring
noipp2 小时前
推荐题目:洛谷 P3726 [AHOI2017/HNOI2017] 抛硬币
c语言·数据结构·c++·算法·编程·洛谷·luogu
Rabitebla3 小时前
C++ STL 之 set 详解:从底层红黑树到实际应用
开发语言·数据结构·c++·算法·leetcode