深入理解 Java Map、Set 与哈希表:原理 + 手写代码 + 实战
·
📖 目录
- 前言
- 二叉搜索树(BST)详解
- 2.1 定义与性质
- 2.2 核心操作:查找、插入、删除
- 2.3 二叉搜索树完整实现代码
- 2.4 性能分析
- 2.5 与 Java 集合的关系
- Map 集合体系全面解析
- 3.1 Map 接口核心特点
- 3.2 Map.Entry 键值对封装
- 3.3 常用方法速查表
- 3.4 TreeMap vs HashMap 对比
- 3.5 TreeMap 实战代码
- Set 集合体系全面解析
- 4.1 Set 核心特性
- 4.2 常用方法速查表
- 4.3 TreeSet vs HashSet 对比
- 4.4 TreeSet 实战代码
- 哈希表(Hash Table)原理精讲
- 5.1 哈希表核心思想
- 5.2 哈希冲突与避免策略
- 5.3 冲突解决:闭散列 vs 开散列
- 5.4 自定义哈希桶(极简 HashMap)手写代码
- 5.5 性能分析
- 5.6 与 Java 集合的关联
- 高频 OJ 刷题方向
- 总结
1. 前言
在 Java 开发与面试中,Map 和 Set 是永远绕不开的核心集合。它们负责高效查找、去重、键值映射,底层依赖三大数据结构:
- 二叉搜索树 / 红黑树
- 哈希表(哈希桶)
本文从零开始,带你吃透原理、手写代码、掌握场景,彻底搞定 Map/Set。
2. 二叉搜索树(BST)详解
2.1 定义与性质
二叉搜索树(Binary Search Tree,BST)也叫二叉排序树,满足三条规则:
- 左子树所有节点值 < 根节点值
- 右子树所有节点值 > 根节点值
- 左右子树也必须是二叉搜索树
2.2 核心操作
- 查找:比根小走左,比根大走右,相等即找到。
- 插入:按查找逻辑走到空位置,插入新节点。
- 删除(面试重点):
- 无左孩子 → 右孩子顶替
- 无右孩子 → 左孩子顶替
- 左右都有 → 找右子树最小节点替换,再删除替换节点
2.3 二叉搜索树完整实现代码(可直接运行)
java
运行
/**
* 二叉搜索树 BST 完整实现
* 包含:查找、插入、删除
*/
public class BinarySearchTree {
// 树节点结构
public static class BSTNode {
int key;
BSTNode left;
BSTNode right;
public BSTNode(int key) {
this.key = key;
this.left = null;
this.right = null;
}
}
// 根节点
private BSTNode root;
public BinarySearchTree() {
this.root = null;
}
// ==================== 查找 ====================
public BSTNode find(int key) {
BSTNode current = root;
while (current != null) {
if (key == current.key) {
return current;
} else if (key < current.key) {
current = current.left;
} else {
current = current.right;
}
}
return null;
}
// ==================== 插入 ====================
public boolean add(int key) {
if (root == null) {
root = new BSTNode(key);
return true;
}
BSTNode current = root;
BSTNode parent = null;
while (current != null) {
if (key == current.key) {
// 重复值不插入
return false;
} else if (key < current.key) {
parent = current;
current = current.left;
} else {
parent = current;
current = current.right;
}
}
BSTNode newNode = new BSTNode(key);
if (key < parent.key) {
parent.left = newNode;
} else {
parent.right = newNode;
}
return true;
}
// ==================== 删除 ====================
public boolean delete(int key) {
BSTNode current = root;
BSTNode parent = null;
// 定位待删除节点与父节点
while (current != null) {
if (key == current.key) {
break;
} else if (key < current.key) {
parent = current;
current = current.left;
} else {
parent = current;
current = current.right;
}
}
// 不存在则删除失败
if (current == null) {
return false;
}
deleteNode(parent, current);
return true;
}
// 删除节点具体逻辑
private void deleteNode(BSTNode parent, BSTNode cur) {
// 1. 左孩子为空
if (cur.left == null) {
if (cur == root) {
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
} else {
parent.right = cur.right;
}
}
// 2. 右孩子为空
else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (cur == parent.left) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
}
// 3. 左右孩子都存在
else {
// 找右子树最小节点
BSTNode minP = cur;
BSTNode minNode = cur.right;
while (minNode.left != null) {
minP = minNode;
minNode = minNode.left;
}
// 替换值
cur.key = minNode.key;
// 删除替代节点
if (minP == cur) {
minP.right = minNode.right;
} else {
minP.left = minNode.right;
}
}
}
}
2.4 性能分析
- 最优(完全二叉树):O(logN)
- 最差(退化成单链):O(N)
为了解决失衡问题,Java 使用红黑树(平衡二叉搜索树)保证稳定 O (logN)。
2.5 与 Java 集合关系
- TreeMap / TreeSet 底层 = 红黑树
- 有序、可排序、稳定高效
3. Map 集合体系全面解析
3.1 Map 接口核心特点
- 不继承 Collection
- 存储 Key-Value 键值对
- Key 唯一不重复,Value 可重复
3.2 Map.Entry<K,V>
Map 的内部接口,用来封装一个键值对:
getKey()获取键getValue()获取值setValue(V)修改值
3.3 常用方法速查表
表格
| 方法 | 功能 |
|---|---|
| V put(K, V) | 添加 / 覆盖键值对 |
| V get(Object) | 根据键取值 |
| V remove(Object) | 删除指定键 |
| Set<K> keySet() | 获取所有键 |
| Set<Map.Entry<K,V>> entrySet() | 获取所有键值对 |
| boolean containsKey(Object) | 判断是否包含键 |
3.4 TreeMap vs HashMap 对比
表格
| 特性 | TreeMap | HashMap |
|---|---|---|
| 底层结构 | 红黑树 | 哈希桶 |
| 时间复杂度 | O(logN) | O(1) |
| 有序性 | Key 有序 | 无序 |
| Null Key | 不允许 | 允许一个 |
| 适用场景 | 需要排序 | 追求最快查询 |
3.5 TreeMap 实战代码
java
运行
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<>();
// 添加数据
map.put("林冲", "豹子头");
map.put("鲁智深", "花和尚");
map.put("武松", "行者");
// 获取与判断
System.out.println(map.get("鲁智深"));
System.out.println(map.containsKey("林冲"));
// 遍历键值对
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
}
}
4. Set 集合体系全面解析
4.1 Set 核心特性
- 继承自 Collection
- 只存 Key,不存 Value
- 自动去重
- 底层直接复用 Map 实现
4.2 常用方法速查表
表格
| 方法 | 功能 |
|---|---|
| boolean add(E) | 添加元素(重复失败) |
| boolean contains(Object) | 判断是否存在 |
| boolean remove(Object) | 删除元素 |
| int size() | 元素个数 |
| boolean isEmpty() | 是否为空 |
4.3 TreeSet vs HashSet 对比
表格
| 特性 | TreeSet | HashSet |
|---|---|---|
| 底层 | 红黑树 | 哈希桶 |
| 有序 | 有序 | 无序 |
| Null | 不允许 | 允许 |
| 效率 | O(logN) | O(1) |
4.4 TreeSet 实战代码
java
运行
import java.util.Set;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // 自动去重
System.out.println(set.contains("apple"));
System.out.println(set);
}
}
5. 哈希表(Hash Table)原理精讲
5.1 哈希表核心思想
通过哈希函数,把 Key 直接映射到数组下标,实现近似 O (1) 的查找、插入、删除。
5.2 哈希冲突
不同 Key 计算出相同下标 → 哈希冲突。冲突无法避免,只能降低。
降低方式:
- 设计优秀哈希函数
- 控制负载因子(Java 默认 0.75)
- 合理扩容
5.3 冲突解决
- 闭散列(开放地址):线性探测、二次探测
- 开散列(哈希桶):数组 + 链表(Java HashMap 采用)
5.4 自定义哈希桶(极简 HashMap)手写代码
java
运行
/**
* 自定义哈希桶(数组 + 链表)
* 实现:put、get、扩容、负载因子控制
*/
public class MyHashMap {
// 链表节点
private static class HashNode {
int key;
int value;
HashNode next;
public HashNode(int key, int value) {
this.key = key;
this.value = value;
this.next = null;
}
}
private HashNode[] table;
private int size;
private static final double LOAD_FACTOR = 0.75;
public MyHashMap() {
table = new HashNode[8];
size = 0;
}
// 计算负载因子
private double currentLoadFactor() {
return size * 1.0 / table.length;
}
// 扩容
private void resize() {
HashNode[] newTable = new HashNode[table.length * 2];
for (int i = 0; i < table.length; i++) {
HashNode cur = table[i];
while (cur != null) {
HashNode next = cur.next;
int index = cur.key % newTable.length;
cur.next = newTable[index];
newTable[index] = cur;
cur = next;
}
}
table = newTable;
}
// 添加/修改
public int put(int key, int value) {
int index = key % table.length;
// 查找更新
HashNode cur = table[index];
while (cur != null) {
if (cur.key == key) {
int oldVal = cur.value;
cur.value = value;
return oldVal;
}
cur = cur.next;
}
// 头插法新节点
HashNode newNode = new HashNode(key, value);
newNode.next = table[index];
table[index] = newNode;
size++;
// 超过负载因子扩容
if (currentLoadFactor() >= LOAD_FACTOR) {
resize();
}
return -1;
}
// 获取
public int get(int key) {
int index = key % table.length;
HashNode cur = table[index];
while (cur != null) {
if (cur.key == key) {
return cur.value;
}
cur = cur.next;
}
return -1;
}
}
5.5 性能分析
- 理想情况:O(1)
- 冲突严重:退化为 O (N),Java 会转为红黑树优化
5.6 与 Java 集合的关联
- HashMap / HashSet = 哈希桶
- 链表长度 ≥ 8 → 转为红黑树
- 自定义对象作 Key 必须重写 hashCode () + equals ()
6. 总结
- 二叉搜索树是 TreeMap/TreeSet 的基础,红黑树保证稳定效率。
- Map 存键值对,Set 存单值自动去重。
- 有序选 Tree 系列,高效选 Hash 系列。
- 哈希表依靠哈希函数 + 冲突解决实现 O (1) 效率。
- 自定义 Key 必须重写 hashCode + equals。
更多推荐




所有评论(0)