菜单

Administrator
发布于 2026-05-18 / 1 阅读
0
0

Java 迭代字典 (Map) 的方式

Java 迭代字典(Map)的多种方式及其性能对比

在 Java 开发中,Map(字典)是最常用的数据结构之一。遍历 Map 中的键值对是日常编码的高频操作。本文将系统梳理 Java 中迭代 Map 的六种常见方式,通过代码示例说明每种写法的特点,并从性能和可读性角度给出最佳实践建议。


1. 准备工作:定义一个示例 Map

所有示例基于以下 HashMap

Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
map.put("date", 4);
map.put("elderberry", 5);

下文每段示例代码均可独立运行。


2. 六种迭代方式

2.1 使用 entrySet() + for-each(增强 for 循环)

最经典、推荐度最高的方式。

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " -> " + value);
}

特点:

  • 一次迭代同时拿到 key 和 value,无需重复查找。
  • 代码简洁,无外部依赖。
  • JDK 1.5+ 可用。

2.2 使用 entrySet() + Iterator

在需要迭代过程中删除元素时使用。

Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> entry = iterator.next();
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " -> " + value);

    // 示例:删除值为偶数的条目
    if (value % 2 == 0) {
        iterator.remove();
    }
}

特点:

  • 唯一能在遍历时安全删除元素的方式(通过 iterator.remove())。
  • 较 for-each 稍显冗长,但语义明确。

注意: for-each 循环中直接调用 map.remove() 会抛出 ConcurrentModificationException


2.3 使用 keySet() + get()(通过键遍历)

for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println(key + " -> " + value);
}

特点:

  • 只关心 key 时的首选。
  • 性能缺陷: 每次 map.get(key) 都需要在哈希表中重新查找,时间复杂度为 O(1)(平均)但仍有哈希计算和可能的链表/红黑树遍历开销。总耗时约为 entrySet 方式的 1.5~2 倍(数据量越大差距越明显)。

2.4 使用 values() 遍历值

for (Integer value : map.values()) {
    System.out.println("value = " + value);
}

特点:

  • 当只需要 value、不需要 key 时最简洁。
  • 返回的是 Collection<V> 视图,直接迭代,性能与 entrySet 相当。

2.5 Java 8 forEach + Lambda

map.forEach((key, value) -> {
    System.out.println(key + " -> " + value);
});

特点:

  • 语法最简洁,一行完成。
  • 内部实现本质是对 entrySet 进行迭代(Map#forEach 默认实现等价于 for (Map.Entry<K, V> entry : entrySet()))。
  • 支持 lambda 块内使用外部变量(需为 effectively final)。
  • 适合函数式风格、流式处理链。

注意: Lambda 体内不能使用 break/continue,若需要提前退出仍需传统 for 循环。


2.6 Stream API(entrySet().stream()

map.entrySet().stream()
    .filter(entry -> entry.getValue() > 2)
    .forEach(entry -> System.out.println(entry.getKey() + " -> " + entry.getValue()));
// 更函数式的写法:收集为新的 Map
Map<String, Integer> filtered = map.entrySet().stream()
    .filter(entry -> entry.getValue() > 2)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

System.out.println(filtered);

特点:

  • 适合链式操作(过滤、映射、排序、收集等)。
  • 惰性求值,只有遇到终止操作(如 forEachcollect)才真正遍历。
  • 性能上比普通 for-each 多出 stream 封装和 lambda 调用开销,在小数据集中可忽略,但在**超大规模数据(百万级以上)**且无额外操作时,传统 for-each 略快。
  • 可并行(.parallelStream()),在多核 + 大数据场景下可显著提速。

3. 性能对比(定性分析)

迭代方式 是否同时获得 key/value 是否支持遍历时删除 性能(相对) 适用场景
entrySet + for-each ★★★★★ 常规遍历,首选
entrySet + Iterator ★★★★★ 需要遍历时删除
keySet + get() ✅(但 get 有额外开销) ★★★☆☆ 仅需 key 时;数据量小
values() ❌(只有 value) ★★★★★ 仅需 value
forEach + Lambda ★★★★★ 函数式风格,简洁
Stream API ★★★★☆ 过滤/转换/并行等复杂操作

关键结论

  1. 首选 entrySet + for-each:可读性好,性能最优。
  2. 避免 keySet + get() 遍历:每次 get 是额外查找,数据量大时性能下降明显。
  3. 需要删除元素时用 Iterator,或使用 Java 8 的 removeIf()map.values().removeIf(...))。
  4. Java 8 forEach 是函数式风格的最佳选择,性能与传统 for-each 相当。
  5. Stream API 在简单遍历场景下没有性能优势,但在链式操作和并行处理时不可替代。
  6. values() 只需 value 时最干净。

4. 最佳实践总结

  • 无脑首选for (Map.Entry<K, V> e : map.entrySet()) { ... }
  • 函数式风格map.forEach((k, v) -> { ... })
  • 需要过滤/转换map.entrySet().stream().filter(...).collect(...)
  • 需要遍历时删除Iteratormap.entrySet().removeIf(...)(Java 8+)
  • 只需 keyfor (K key : map.keySet()) { ... }
  • 只需 valuefor (V value : map.values()) { ... }

5. 完整可运行示例

将下面代码保存为 IterateMapDemo.java,直接编译运行:

import java.util.*;

public class IterateMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("cherry", 3);
        map.put("date", 4);
        map.put("elderberry", 5);

        // 1. entrySet + for-each
        System.out.println("=== 1. entrySet + for-each ===");
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }

        // 2. entrySet + Iterator(支持删除)
        System.out.println("=== 2. entrySet + Iterator ===");
        Iterator<Map.Entry<String, Integer>> iter = map.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<String, Integer> entry = iter.next();
            if (entry.getValue() % 2 == 0) {
                iter.remove(); // 安全删除
            } else {
                System.out.println(entry.getKey() + " -> " + entry.getValue());
            }
        }

        // 重新填充数据用于后续演示
        map.put("banana", 2);
        map.put("date", 4);

        // 3. keySet + get()
        System.out.println("=== 3. keySet + get() ===");
        for (String key : map.keySet()) {
            System.out.println(key + " -> " + map.get(key));
        }

        // 4. values()
        System.out.println("=== 4. values() ===");
        for (Integer value : map.values()) {
            System.out.println("value = " + value);
        }

        // 5. forEach + Lambda
        System.out.println("=== 5. forEach + Lambda ===");
        map.forEach((key, value) -> System.out.println(key + " -> " + value));

        // 6. Stream API
        System.out.println("=== 6. Stream API ===");
        map.entrySet().stream()
            .filter(e -> e.getValue() > 2)
            .forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue()));
    }
}

6. 小结

场景 推荐方式
通用遍历 entrySet + for-each
函数式风格 Map.forEach()
过滤/转换 Stream API
遍历时删除 Iterator.remove() / removeIf()
只需 key keySet()
只需 value values()

选择最合适的方式,在保证代码可读性的前提下兼顾性能,才是好的工程实践。


Happy Coding! 🚀


评论