【Java】HashMap的详细介绍
·
HashMap 是 Java 集合框架中非常重要的实现类,基于哈希表(Hash Table)实现,用于存储键值对(key-value)映射,提供快速的插入、删除和查找操作。以下是对 HashMap 的详细介绍:
一、HashMapHashMap 的基本特性
- 存储结构:基于数组(哈希桶)+ 链表 / 红黑树实现(Java 8 及以上)
- 无序性:存储顺序与插入顺序无关(与 TreeMap 的有序性不同)
- 键唯一性:key 不允许重复,若插入相同 key,新 value 会覆盖旧 value
- null 支持:允许 key 为 null(仅允许一个),允许多 value 为 null(允许多个)
- 非线程安全:多线程环境下可能出现并发问题(如需线程安全,可使用
ConcurrentHashMap或Collections.synchronizedMap()) - 初始容量与负载因子:
- 初始容量:默认 16(哈希桶数组的初始长度)
- 负载因子:默认 0.75(扩容阈值 = 容量 × 负载因子,当元素数量超过阈值时触发扩容)
二、HashMap 的底层原理
1. 哈希表结构(Java 8+)
HashMap 的核心是 哈希桶数组(Node<K,V>[] table),每个数组元素是一个链表或红黑树的头节点:
- 当链表长度超过阈值(默认 8)且数组容量 ≥ 64 时,链表会转为红黑树(提高查询效率)
- 当红黑树节点数量少于 6 时,会退化为链表(节省空间)
plaintext
哈希桶数组: [Node1, Node2, ..., Node16]
↓ ↓
链表/红黑树 链表/红黑树
2. 核心操作原理
(1)存储键值对(put 方法)
- 计算哈希值:对 key 调用
hashCode()计算哈希值,再通过扰动函数(减少哈希冲突)得到最终哈希值 - 确定索引:根据哈希值和数组长度计算数组索引(
(n - 1) & hash,等价于取模运算,效率更高) - 处理哈希冲突:
- 若索引位置为空,直接插入新节点
- 若索引位置已有节点:
- 若 key 相同(
equals()为 true),覆盖 value - 若 key 不同,插入链表 / 红黑树(链表长度达标则转红黑树)
- 若 key 相同(
- 扩容检查:插入后若元素数量超过阈值,触发扩容(数组长度翻倍,重新计算所有元素的索引并迁移)
(2)查询键值对(get 方法)
- 计算 key 的哈希值和数组索引
- 遍历索引位置的链表 / 红黑树,通过 equals() 匹配 key,返回对应的 value
- 若未找到,返回 null
(3)扩容机制
- 当元素数量 > 容量 × 负载因子时,数组容量翻倍(始终为 2 的幂次方)
- 扩容时需重新计算所有元素的索引(因容量变化,
(n-1) & hash结果改变) - Java 8 优化了扩容时的索引计算(通过高位运算判断,避免重新哈希)
三、关键方法与示例
1. 常用方法
java
运行
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// 创建 HashMap
Map<String, Integer> map = new HashMap<>();
// 添加元素(put)
map.put("apple", 10);
map.put("banana", 20);
map.put("orange", 30);
// 查找元素(get)
int count = map.get("apple"); // 10
// 替换元素(put 或 replace)
map.put("apple", 15); // 覆盖旧值
map.replace("banana", 25); // 仅替换已存在的 key
// 移除元素(remove)
map.remove("orange");
// 检查 key 是否存在(containsKey)
boolean hasBanana = map.containsKey("banana"); // true
// 遍历元素
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 其他常用方法
int size = map.size(); // 元素数量
boolean isEmpty = map.isEmpty(); // 是否为空
map.clear(); // 清空所有元素
}
}
2. 自定义对象作为 Key
当使用自定义对象作为 key 时,必须重写 hashCode() 和 equals() 方法,否则会导致无法正确查找或删除元素:
java
运行
class Student {
private String id;
private String name;
// 构造方法、getter、setter 省略
// 重写 hashCode() 和 equals()
@Override
public int hashCode() {
return id.hashCode(); // 基于唯一标识 id 计算哈希值
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return id.equals(student.id); // 基于 id 判断是否相同
}
}
// 使用自定义对象作为 key
Map<Student, Integer> studentScores = new HashMap<>();
studentScores.put(new Student("1001", "Alice"), 90);
![]()
四、HashMap 与其他 Map 的对比
| 实现类 | 底层结构 | 有序性 | 线程安全 | 允许 null |
|---|---|---|---|---|
| HashMap | 数组 + 链表 / 红黑树 | 无序 | 否 | 是 |
| Hashtable | 数组 + 链表 | 无序 | 是(低效) | 否 |
| TreeMap | 红黑树 | 有序 | 否 | key 不允许 |
| LinkedHashMap | 哈希表 + 双向链表 | 插入 / 访问顺序 | 否 | 是 |
五、注意事项与最佳实践
- 初始容量设置:若已知元素数量,可指定初始容量(如
new HashMap<>(100)),减少扩容次数 - 哈希冲突避免:自定义 key 时,确保
hashCode()分布均匀,减少哈希冲突 - 线程安全:多线程环境下优先使用
ConcurrentHashMap,而非Hashtable - 遍历效率:
- 遍历 key:
for (K key : map.keySet()) - 遍历键值对:
for (Map.Entry<K,V> entry : map.entrySet())(推荐,效率更高)
- 遍历 key:
- Java 8 改进:引入红黑树优化长链表的查询性能(时间复杂度从 O (n) 提升至 O (log n))
HashMap 凭借高效的哈希算法和动态扩容机制,成为日常开发中最常用的 Map 实现类,适用于大多数键值对存储场景。理解其底层原理有助于更好地使用和优化它。
更多推荐



所有评论(0)