欢迎光临
我们一直在努力

HashMap的四种遍历方式

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万条数据时:

遍历方式耗时(ms)性能排名推荐度
entrySet() 8 ⭐⭐⭐⭐⭐ 最推荐
Lambda forEach 9 ⭐⭐⭐⭐⭐ 推荐
values() 6 ⭐⭐⭐⭐⭐ 仅需Value时推荐
keySet() + get() 15 ⭐⭐⭐ 不推荐

2. 性能分析

  • entrySet() – 性能最优,一次遍历获取 Key+Value
  • Lambda forEach – 性能接近 entrySet(),代码更简洁
  • values() – 仅遍历 Value 时性能最优
  • keySet() + get() – 需要二次哈希计算,性能最差

  • 三、注意事项

    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 安全可靠

    核心要点

  • 性能最优:优先使用 entrySet() 遍历(一次获取 Key+Value,无额外哈希计算)
  • 场景适配:仅需 Value 用 values(),仅需 Key 用 keySet(),Java 8+ 追求简洁用 Lambda(forEach)
  • 避坑要点:遍历中修改元素需用迭代器,避免并发修改异常;遍历前判空防止空指针
  • 线程安全:多线程环境使用 ConcurrentHashMap
  • 代码风格:现代 Java 项目推荐使用 Lambda 表达式,提升代码可读性
  • 最佳实践

    // 推荐写法(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,遍历后统一删除

    作者:[识君啊]

    赞(0)
    未经允许不得转载:171主机测评 » HashMap的四种遍历方式
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址