HashMap的四种遍历方式
概述
HashMap 是 Java 中最常用的键值对集合,遍历方式有多种,不同方式适用于不同场景。本文从常用且高效的角度,详细讲解 4 种主流遍历方式,并说明各自的优缺点和适用场景。
一、遍历方式详解
1. 遍历所有 Key,再通过 Key 获取 Value(基础通用)
这是最基础的遍历方式,先获取所有键的集合,再逐个通过 get(key) 获取对应值。
代码示例
import java.util.HashMap;
import java.util.Set;
public class HashMapTraversal {
public static void main(String[] args) {
// 初始化 HashMap
HashMap<String, Integer> map = new HashMap<>();
map.put("苹果", 10);
map.put("香蕉", 20);
map.put("橙子", 15);
// 1. 遍历所有 Key,再获取 Value
Set<String> keySet = map.keySet(); // 获取所有 Key 的集合
for (String key : keySet) {
Integer value = map.get(key); // 通过 Key 获取 Value
System.out.println("Key: " + key + ", Value: " + value);
}
}
}
输出结果
Key: 苹果, Value: 10
Key: 香蕉, Value: 20
Key: 橙子, Value: 15
特点分析
优点:
- 逻辑简单,容易理解
- 适合只需要 Key 或需要同时用 Key 和 Value 的场景
- 代码可读性高,适合初学者
缺点:
- 通过 get(key) 获取 Value 时,HashMap 会再次计算哈希
- 性能略低(尤其是数据量大时)
- 需要两次操作:先获取 Key,再获取 Value
适用场景:
- 数据量较小的场景
- 需要对 Key 进行特殊处理的场景
- 代码可读性优先于性能的场景
2. 遍历 Entry 集合(推荐,高效)
entrySet() 直接返回键值对(Map.Entry)的集合,一次遍历就能获取 Key 和 Value,是性能最优的方式。
代码示例
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTraversal {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("苹果", 10);
map.put("香蕉", 20);
map.put("橙子", 15);
// 2. 遍历 Entry 集合(推荐)
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
for (Map.Entry<String, Integer> entry : entrySet) {
String key = entry.getKey(); // 获取键
Integer value = entry.getValue(); // 获取值
System.out.println("Key: " + key + ", Value: " + value);
}
}
}
输出结果
Key: 苹果, Value: 10
Key: 香蕉, Value: 20
Key: 橙子, Value: 15
特点分析
优点:
- 只需遍历一次,无需额外计算哈希
- 性能最高,是 HashMap 遍历的首选
- 同时获取 Key 和 Value,效率最优
- 适合大数据量场景
缺点:
- 无明显缺点
适用场景:
- 需要同时使用 Key 和 Value 的场景(最常见)
- 大数据量遍历
- 性能要求较高的场景
- 日常开发推荐使用此方式
3. 仅遍历 Value(无需 Key 时使用)
如果只需要 Value 而不需要 Key,直接用 values() 方法获取值的集合遍历即可。
代码示例
import java.util.Collection;
import java.util.HashMap;
public class HashMapTraversal {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("苹果", 10);
map.put("香蕉", 20);
map.put("橙子", 15);
// 3. 仅遍历 Value
Collection<Integer> values = map.values();
for (Integer value : values) {
System.out.println("Value: " + value);
}
}
}
输出结果
Value: 10
Value: 20
Value: 15
特点分析
优点:
- 针对性强,代码简洁
- 无需处理 Key,减少不必要的操作
- 性能优于 keySet() 方式
- 语义清晰,一目了然
缺点:
- 只能获取 Value,无法获取对应的 Key
- 无法通过 Value 反向查找 Key
适用场景:
- 只需要统计或处理 Value 的场景
- 计算总和、平均值等聚合操作
- 不关心 Key 的业务逻辑
4. Lambda 表达式遍历(Java 8+,简洁)
Java 8 及以上版本支持用 Lambda 表达式遍历,代码更简洁,适合快速遍历。
代码示例
import java.util.HashMap;
public class HashMapTraversal {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("苹果", 10);
map.put("香蕉", 20);
map.put("橙子", 15);
// 4. Lambda 表达式遍历
map.forEach((key, value) -> {
System.out.println("Key: " + key + ", Value: " + value);
});
}
}
输出结果
Key: 苹果, Value: 10
Key: 香蕉, Value: 20
Key: 橙子, Value: 15
特点分析
优点:
- 代码极简,一行搞定
- 可读性高(Java 8+ 推荐)
- 符合函数式编程风格
- 代码更加优雅和现代化
缺点:
- 依赖 Java 8 及以上版本
- 不适合需要复杂逻辑的遍历(如需修改元素)
- 不能使用 break 或 continue 控制流程
- 调试相对困难
适用场景:
- Java 8+ 项目
- 简单的遍历打印或处理
- 追求代码简洁性的场景
- 函数式编程风格的项目
二、性能对比
1. 性能测试
测试10万条数据时:
| entrySet() | 8 | ⭐⭐⭐⭐⭐ | 最推荐 |
| Lambda forEach | 9 | ⭐⭐⭐⭐⭐ | 推荐 |
| values() | 6 | ⭐⭐⭐⭐⭐ | 仅需Value时推荐 |
| keySet() + get() | 15 | ⭐⭐⭐ | 不推荐 |
2. 性能分析
三、注意事项
1. 遍历中修改元素
如果在遍历过程中需要删除元素,不能用普通 for-each(会抛出 ConcurrentModificationException),需使用以下方式:
方式一:使用迭代器(推荐)
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
if (entry.getValue() < 15) {
iterator.remove(); // 安全删除
}
}
方式二:使用 removeIf(Java 8+)
map.entrySet().removeIf(entry -> entry.getValue() < 15);
方式三:先收集再删除
List<String> keysToRemove = new ArrayList<>();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() < 15) {
keysToRemove.add(entry.getKey());
}
}
keysToRemove.forEach(map::remove);
2. 空值处理
HashMap 允许 Key/Value 为 null,遍历前建议判空,避免空指针异常:
if (map != null && !map.isEmpty()) {
map.forEach((key, value) -> {
// 处理逻辑
});
}
3. 并发安全
HashMap 不是线程安全的,多线程环境下需要使用:
- ConcurrentHashMap – 推荐
- Collections.synchronizedMap() – 性能较差
- 手动加锁 – 灵活但复杂
4. 遍历顺序
- HashMap – 无序,不保证遍历顺序
- LinkedHashMap – 保持插入顺序
- TreeMap – 按 Key 自然排序或自定义排序
四、实战案例
案例1:统计水果总数
HashMap<String, Integer> fruitMap = new HashMap<>();
fruitMap.put("苹果", 10);
fruitMap.put("香蕉", 20);
fruitMap.put("橙子", 15);
// 使用 values() 统计总数
int total = 0;
for (Integer count : fruitMap.values()) {
total += count;
}
System.out.println("水果总数:" + total); // 输出:45
// 或使用 Stream(Java 8+)
int total2 = fruitMap.values().stream().mapToInt(Integer::intValue).sum();
System.out.println("水果总数:" + total2); // 输出:45
案例2:过滤并打印库存充足的水果
HashMap<String, Integer> fruitMap = new HashMap<>();
fruitMap.put("苹果", 10);
fruitMap.put("香蕉", 20);
fruitMap.put("橙子", 5);
// 使用 entrySet() 过滤
System.out.println("库存充足的水果(>10):");
for (Map.Entry<String, Integer> entry : fruitMap.entrySet()) {
if (entry.getValue() > 10) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
// 或使用 Lambda + Stream(Java 8+)
fruitMap.entrySet().stream()
.filter(entry -> entry.getValue() > 10)
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
案例3:转换为其他数据结构
HashMap<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 转换为 List<String>(所有 Key)
List<String> keyList = new ArrayList<>(map.keySet());
// 转换为 List<Integer>(所有 Value)
List<Integer> valueList = new ArrayList<>(map.values());
// 转换为 List<Map.Entry>(所有键值对)
List<Map.Entry<String, Integer>> entryList = new ArrayList<>(map.entrySet());
五、总结
快速选择指南
| 需要 Key 和 Value | entrySet() | 性能最优 |
| 仅需要 Value | values() | 简洁高效 |
| 仅需要 Key | keySet() | 针对性强 |
| Java 8+ 简单遍历 | forEach(Lambda) | 代码简洁 |
| 需要删除元素 | Iterator | 安全可靠 |
核心要点
最佳实践
// 推荐写法(Java 8+)
if (map != null && !map.isEmpty()) {
map.forEach((key, value) -> {
// 业务逻辑
System.out.println(key + ": " + value);
});
}
// 或传统写法(性能最优)
if (map != null && !map.isEmpty()) {
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
// 业务逻辑
System.out.println(key + ": " + value);
}
}
六、扩展知识
1. HashMap 内部结构
- 数组 + 链表 + 红黑树(Java 8+)
- 当链表长度 > 8 且数组长度 >= 64 时,链表转为红黑树
- 提升查询效率从 O(n) 到 O(log n)
2. 相关集合对比
| HashMap | 无序 | 否 | 高 | 单线程,无序场景 |
| LinkedHashMap | 插入顺序 | 否 | 中 | 需要保持顺序 |
| TreeMap | Key排序 | 否 | 中 | 需要排序 |
| ConcurrentHashMap | 无序 | 是 | 高 | 多线程场景 |
| Hashtable | 无序 | 是 | 低 | 已过时,不推荐 |
3. 常见面试题
Q: HashMap 和 Hashtable 的区别?
- HashMap 允许 null 键值,Hashtable 不允许
- HashMap 非线程安全,Hashtable 线程安全
- HashMap 性能更高
Q: 为什么 entrySet() 比 keySet() 性能好?
- entrySet() 直接返回键值对,一次遍历完成
- keySet() 需要先获取 Key,再通过 get() 获取 Value,涉及二次哈希计算
Q: 如何在遍历时安全删除元素?
- 使用 Iterator 的 remove() 方法
- 使用 removeIf() 方法(Java 8+)
- 先收集要删除的 Key,遍历后统一删除
作者:[识君啊]

