魔鸡马奇克2免安装绿色中文版
175M · 2025-09-17
HashMap
是Java
中最常用的数据结构之一。相信不少同学都用过keySet()
方法来遍历HashMap
,但你可能不知道,这里隐藏着一个性能大坑!
先简单说一下,HashMap
是Java
中最常用的键值对存储结构,而keySet()
是HashMap
提供的一个方法,它会返回所有键的Set
集合。
很多程序员喜欢这样遍历HashMap
:
Map<String, Integer> map = new HashMap<>();
// 示例数据
map.put("a", 1);
map.put("b", 2);
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " : " + value);
}
看起来没问题,语法清晰,逻辑也通顺。 但问题就出在这里!尤其是数据量一大,程序就开始卡、慢、耗CPU。
为什么?因为keySet()
这种方式,每遍历一次,就要调用一次get()。
get()
看似简单,背后可是要重新计算哈希、找桶、遍历链表或红黑树。
你每访问一个key,它都重新走一遍查找流程。
这就像你进图书馆,每拿一本书,都要重新查一遍索引卡,很累很慢。这就是问题根源。
keySet()
返回的是什么。
Set<K> keySet = map.keySet();
它返回的是HashMap
内部的一个KeySet视图(View)。
这个视图不是复制出来的,它是活的。
你改map
,keySet
跟着变。
但它本身不存数据,只是帮你看到所有的key。
所以,当你用增强for
循环遍历keySet
:
for (String key : map.keySet()) {
// 每次都要 map.get(key)
}
你只是拿到了key
,value
还得重新查!
这就导致:
-重复哈希计算
-重复桶定位
-可能的链表/红黑树遍历
哪怕这个key
刚才还在map
里见过。它不认识你,还得再查一遍。
我们来跑个真实测试。
假设有个HashMap
,存了10万个键值对。
我们分别用三种方式遍历:
for (String key : map.keySet()) {
String value = map.get(key);
// 处理 value
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 处理 key 和 value
}
map.forEach((key, value) -> {
// 处理 key 和 value
});
测试结果:
方式 | 平均耗时 |
---|---|
keySet + get | 85ms |
entrySet | 12ms |
forEach | 10ms |
keySet
比entrySet
慢了 7 倍!数据越大,差距也越大。
为什么?因为entrySet
拿到的是 键值对节点本身。
它直接遍历内部的Node
数组。一个节点,包含key和value。一次拿到,不用再查。而keySet
只拿key,value得再查一次。
查一次,就是一次 O(1) ~ O(log n) 的操作。遍历n次,就是n次查找。
时间复杂度从O(n) 退化成O(n²)
我们打开 HashMap 源码。
keySet()
方法:
KeySet
是个内部类。它遍历时,只是把Node
的key拿出来。但value呢?没有。
所以你调用map.get(key)
,就会走get()
方法:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
getNode()
会重新哈希、定位桶、遍历链表。哪怕这个Node
刚才就在你眼前。它不记得,它只认查,这就是浪费。
而entrySet()
呢?它返回的是EntrySet
视图。遍历时,直接拿到Node
对象。Node
里key,有value。直接用,不查。效率自然高。
来看看常见的踩坑场景。
// 错误示范
for (String key : configMap.keySet()) {
log.info("配置项: {} = {}", key, configMap.get(key));
}
每打一条日志,查一次 value。如果configMap
有几百个配置项,日志一多,系统就卡。
正确写法:
for (Map.Entry<String, String> entry : configMap.entrySet()) {
log.info("配置项: {} = {}", entry.getKey(), entry.getValue());
}
或者更简洁:
configMap.forEach((key, value) ->
log.info("配置项: {} = {}", key, value)
);
比如把 Map 转成 List。
// 慢
List<String> list = new ArrayList<>();
for (String key : map.keySet()) {
list.add(map.get(key));
}
更快的方法
List<String> list = new ArrayList<>();
map.forEach((key, value) -> list.add(value));
或者:
List<String> list = new ArrayList<>(map.values());
如果只想要 value,直接用 values()
就行。
// 多此一举
for (String key : userMap.keySet()) {
if (userMap.get(key).isActive()) {
sendEmail(key);
}
}
一次搞定
userMap.forEach((userId, user) -> {
if (user.isActive()) {
sendEmail(userId);
}
});
也不是说 keySet()
完全不能用。如果你只关心 key,不关心 value,那用keySet
没问题。
比如:
// 检查某个 key 是否存在(但这有更好方式)
if (map.keySet().contains("admin")) { ... }
// 更推荐用 containsKey
if (map.containsKey("admin")) { ... }
或者:
// 打印所有用户名
for (String username : userMap.keySet()) {
System.out.println(username);
}
这种场景,不需要 value,keySet()
是合理的。
但一旦你要用 value,还是别再用keySet() + get()
组合了。
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
-兼容 Java 5+ -性能高 -明确拿到键值对
map.forEach((key, value) -> {
System.out.println(key + " -> " + value);
});
-代码最简洁 -性能最好 -函数式风格,易读
如果只处理 value:
for (String value : map.values()) {
process(value);
}
或者:
List<String> values = new ArrayList<>(map.values());
values() 也是视图,不复制数据,所以是高效的。
有时候你需要在遍历中删除元素。这时候,增强for循环会抛ConcurrentModificationException
。
正确做法是用迭代器:
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getValue() < 0) {
it.remove(); // 安全删除
}
}
别用map.remove(key)
在遍历中删除。会出问题。
keySet() + get()
可能是性能陷阱。get
都是重新查找,浪费资源。entrySet()
或forEach()
。keySet
,只取value用values()。这一个小改动,可能让你的接口从 200ms 降到 30ms。性能优化,就藏在这些细节里。
转发给还在用 keySet() 的同事,救他一命。
《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》
《Java 订单超时未支付,如何自动关闭?掌握这 3 种方案,轻松拿 offer!》
《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》
《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》
175M · 2025-09-17
302M · 2025-09-17
318M · 2025-09-17