导读:本期聚焦于小伙伴创作的《如何在Java中遍历Map集合的键和值_entrySet与keySet的性能差异》,敬请观看详情。一次线上接口超时排查发现,同一个HashMap在十万级数据下用keySet遍历比entrySet慢了近一倍。根本原因在于keySet遍历时每取一个值都要再做一次map.get(key)哈希查找,而entrySet直接持有键值对对象。本文从JDK源码层面比较两种写法的底层实现,给出不同数据规模下的选型建议,并附可读性和并行流场景下的实践示例,帮你彻底弄清该用哪种方式遍历Map。

在Java开发中,Map是最常用的集合类型之一。当我们既需要键也需要值时,通常有两种主流遍历方式:通过keySet拿到所有键再取 value,或者通过entrySet直接拿到键值对。二者在写法上差异不大,但在数据量增长时性能表现截然不同。

如何在Java中遍历Map集合的键和值_entrySet与keySet的性能差异

一、keySet与entrySet的基本用法

keySet方法返回Map中所有键的Set视图,遍历这个Set得到每一个key,再通过Map的get方法获取对应的value。这种方式代码直观,初学者最容易写出。下面的示例展示了如何使用keySet遍历HashMap:

import java.util.HashMap;
import java.util.Map;

public class KeySetDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        // 通过keySet遍历
        for (String key : map.keySet()) {
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

entrySet方法返回的是键值对(Map.Entry)的Set视图,每一次迭代直接拿到一个包含key和value的对象,不需要二次查找。下面是用entrySet遍历的等价写法:

import java.util.HashMap;
import java.util.Map;

public class EntrySetDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        // 通过entrySet遍历
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "=" + value);
        }
    }
}

从代码可读性来看,entrySet虽然多写了一点entry.getKey()和entry.getValue(),但语义更明确:每一次循环就是处理一个完整的映射关系。而keySet在循环体内隐藏了一次get调用,阅读代码时容易忽略它的存在。

二、底层原理与性能差异分析

要理解性能差异,需要看HashMap的源码实现。HashMap的keySet视图本质上是一个内部类KeySet,它的迭代器遍历的是HashMap的table数组中的节点。当你调用map.get(key)时,HashMap需要根据key的hash重新定位桶位置,并可能遍历链表或红黑树来比对key。也就是说,keySet遍历中每取一个value,就额外做了一次O(1)但带常数开销的查找。

而entrySet返回的EntrySet视图,其迭代器直接返回节点自身(因为HashMap.Node本身实现了Map.Entry),节点里已经保存了key和value的引用。遍历时不需要再次哈希定位,直接读取字段即可。在十万甚至百万级数据下,keySet方式会比entrySet多出十万次以上的get操作,累积的查找和函数调用开销非常明显。

我们可以用一段简单的基准测试代码来直观对比(这里用System.currentTimeMillis粗略计时,真实场景建议用JMH):

import java.util.HashMap;
import java.util.Map;

public class PerformanceTest {
    public static void main(String[] args) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < 100000; i++) {
            map.put(i, i);
        }

        long start1 = System.currentTimeMillis();
        for (Integer key : map.keySet()) {
            Integer v = map.get(key);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("keySet耗时: " + (end1 - start1) + "ms");

        long start2 = System.currentTimeMillis();
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            Integer k = entry.getKey();
            Integer v = entry.getValue();
        }
        long end2 = System.currentTimeMillis();
        System.out.println("entrySet耗时: " + (end2 - start2) + "ms");
    }
}

在我的本地环境多次运行中,keySet通常比entrySet慢30%到100%不等,数据量越大差距越稳定。如果Map的实现是TreeMap,get操作还是O(log n),keySet的劣势会被进一步放大。因此在任何需要同时用key和value的场合,entrySet都是更优解。

三、只遍历键或只遍历值的特殊情况

如果你的业务逻辑确实只需要key,比如检查某些键是否存在、或者仅对key做处理,那么使用keySet是合理的,此时没有多余的get调用,和entrySet比没有性能差距。类似地,如果只需要value,应当使用values()方法而不是通过keySet再get。

import java.util.HashMap;
import java.util.Map;

public class ValuesDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        // 只遍历值
        for (Integer value : map.values()) {
            System.out.println(value);
        }
    }
}

很多开发者习惯性地写for (String k : map.keySet()) { ... map.get(k) ... },即使只需要key也会顺手把value取出来,或者反过来只需要value却用entrySet。这种写法虽然不会出错,但遵循最小需求原则能让代码意图更清晰,也避免后续维护者误以为value被使用了。

四、Java 8之后的遍历新写法

从Java 8开始,Map提供了forEach方法,内部其实也是基于entrySet实现的,语法更加简洁。它接受一个BiConsumer,同时传入key和value,既高效又易读。

import java.util.HashMap;
import java.util.Map;

public class ForEachDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("x", 10);
        map.put("y", 20);
        // Java8 forEach,底层用entrySet
        map.forEach((k, v) -> System.out.println(k + "=" + v));
    }
}

如果需要在遍历时做并行处理,可以使用parallelStream,但注意entrySet的并行流底层依旧是拆分entrySet的视图,不会产生额外的get开销。相比之下,若有人误用keySet的并行流并在内部get,不仅慢还可能因HashMap非线程安全引发问题。因此无论串行还是并行,优先选择entrySet或forEach。

五、总结与选型建议

综合来看,当需要同时访问Map的键和值时,entrySet以及基于它的forEach是性能最好、语义最清晰的方案;keySet仅适合只处理键的场景,切忌在循环体内配合get使用。在代码评审中,可以将keySet加get的写法列为不规范用法。理解这一差异不仅能避免无谓的性能损耗,也体现了对集合底层实现的掌握程度。

对于遗留系统里大量存在的keySet遍历代码,如果Map数据量较小(比如配置项、常量映射),重构优先级可以放低;但如果是热点接口中遍历大Map,应尽快改为entrySet。这样简单的改动往往能带来可观的响应时间提升。

JavaMap遍历entrySet_keySet修改时间:2026-08-03 20:15:38

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。