news 2026/2/28 3:09:47

阿里不推荐使用 keySet() 遍历HashMap?是有原因的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
阿里不推荐使用 keySet() 遍历HashMap?是有原因的

引言

HashMap相信所有学Java的都一定不会感到陌生,作为一个非常重用且非常实用的Java提供的容器,它在我们的代码里面随处可见。因此遍历操作也是我们经常会使用到的。HashMap的遍历方式现如今有非常多种:

  1. 使用迭代器(Iterator)。

  2. 使用keySet()获取键的集合,然后通过增强的 for 循环遍历键。

  3. 使用entrySet()获取键值对的集合,然后通过增强的 for 循环遍历键值对。

  4. 使用 Java 8+ 的 Lambda 表达式和流。

以上遍历方式的孰优孰劣,在《阿里巴巴开发手册》中写道:

这里推荐使用的是entrySet进行遍历,在Java8中推荐使用Map.forEach()。给出的理由是遍历次数上的不同。

  1. keySet遍历,需要经过两次遍历。

  2. entrySet遍历,只需要一次遍历。

其中keySet遍历了两次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。

其中后面一段话很好理解,但是前面这句话却有点绕,为什么转换成了Iterator遍历了一次?

我查阅了各个平台对HashMap的遍历,其中都没有或者原封不动的照搬上句话。(当然也可能是我没有查阅到靠谱的文章,欢迎指正)

Part2keySet如何遍历了两次

我们首先写一段代码,使用keySet遍历Map。

public class Test { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); for (String key : map.keySet()) { String value = map.get(key); System.out.println(key + ":" + value); } } }

运行结果显而易见的是

k1:v1 k2:v2 k3:v3

两次遍历,第一次遍历所描述的是转为Iterator对象我们好像没有从代码中看见,我们看到的后面所描述的遍历,也就是遍历map,keySet()所返回的Set集合中的key,然后去HashMap中拿取value的。

Iterator对象呢?如何遍历转换为Iterator对象的呢?

首先我们这种遍历方式大家都应该知道是叫:增强for循环,for-each

这是一种Java的语法糖~。

我们可以通过反编译,或者直接通过Idea在class文件中查看对应的Class文件

public class Test { public Test() { } public static void main(String[] args) { Map<String, String> map = new HashMap(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); Iterator var2 = map.keySet().iterator(); while(var2.hasNext()) { String key = (String)var2.next(); String value = (String)map.get(key); System.out.println(key + ":" + value); } } }

和我们编写的是存在差异的,其中我们可以看到其中通过map.keySet().iterator()获取到了我们所需要看见的Iterator对象。

那么它又是怎么转换成的呢?为什么需要遍历呢?我们查看iterator()方法

1iterator()

发现是Set定义的一个接口。返回此集合中元素的迭代器

2HashMap.KeySet#iterator()

我们查看HashMap中keySet类对该方法的实现。

final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }

其中的iterator()方法返回的是一个KeyIterator对象,那么究竟是在哪里进行了遍历呢?我们接着往下看去。

3HashMap.KeyIterator

final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } }

这个类也很简单:

  1. 继承了HashIterator类。

  2. 实现了Iterator接口。

  3. 一个next()方法。

还是没有看见哪里进行了遍历,那么我们继续查看HashIterator类​

4HashMap.HashIterator

abstract class HashIterator { Node<K,V> next; // next entry to return Node<K,V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } }

我们可以发现这个构造器中存在了一个do-while循环操作,目的是找到一个第一个不为空的entry

HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } }

KeyIterator是extendHashIterator对象的。这里涉及到了继承的相关概念,大家忘记的可以找相关的文章看看,或者我也可以写一篇~~dog。

例如两个类

public class Father { public Father(){ System.out.println("father"); } }
public class Son extends Father{ public static void main(String[] args) { Son son = new Son(); } }

创建Son对象的同时,会执行Father构造器。也就会打印出father这句话。

那么这个循环操作就是我们要找的循环操作了。

Part3总结

  1. 使用keySet遍历,其实内部是使用了对应的iterator()方法。

  2. iterator()方法是创建了一个KeyIterator对象。

  3. KeyIterator对象extendHashIterator对象。

  4. HashIterator对象的构造方法中,会遍历找到第一个不为空的entry

keySet->iterator()->KeyIterator->HashIterator

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/27 9:06:44

鸿蒙中级课程笔记7—给应用添加通知

注意本章实现的为本地通知 通知介绍 通知分类 根据内容样式划分 分类维度具体类型说明与用途&#x1f3a8; 按内容样式分类基础类型用于展示静态的文本或图片信息。模板类型用于展示动态变化的内容&#xff0c;最常见的是进度条。 基础通知分类 这是最常用的通知类型&…

作者头像 李华
网站建设 2026/2/26 1:19:58

AI辅助API设计:提高接口的一致性与可用性

AI辅助API设计:提高接口的一致性与可用性 关键词:AI辅助、API设计、接口一致性、接口可用性、API开发 摘要:本文聚焦于AI辅助API设计这一前沿话题,旨在深入探讨如何利用AI技术提高API接口的一致性与可用性。文章首先介绍了API设计的背景知识,包括目的、预期读者、文档结构和…

作者头像 李华
网站建设 2026/2/27 2:40:19

复现模拟退火、粒子群算法解约束最优化问题 内容: 程序一:模拟退火算法SA算法求解附图所示变速...

复现模拟退火、粒子群算法解约束最优化问题 内容&#xff1a; 程序一&#xff1a;模拟退火算法SA算法求解附图所示变速箱设计带约束最优化实际工程问题的自编MATLAB程序。 程序二&#xff1a;粒子群算法PSO算法求解附图所示变速箱设计带约束最优化实际工程问题的自编MATLAB程序…

作者头像 李华
网站建设 2026/2/27 11:16:24

双十一秒杀:如何让1万人同时抢1瓶水,系统却不崩?

想象一间小超市突然涌入1万人&#xff0c;目标都是货架上最后一瓶矿泉水&#xff0c;而你作为店长要保证不踩踏、不乱价、且有人真的能买到——这就是秒杀系统的核心挑战。 一、场景还原&#xff1a;为什么简单请求会“爆”&#xff1f; 普通商品页用户浏览10秒&#xff0c;下单…

作者头像 李华
网站建设 2026/2/26 8:38:34

五个值得关注的Python新库

SQLAlchemy是Python中最流行的ORM&#xff08;对象关系映射&#xff09;框架之一&#xff0c;它提供了高效且灵活的数据库操作方式。本文将介绍如何使用SQLAlchemy ORM进行数据库操作。 目录 安装SQLAlchemy 核心概念 连接数据库 定义数据模型 创建数据库表 基本CRUD操作…

作者头像 李华
网站建设 2026/2/24 15:44:08

C++中的职责链模式

1、非修改序列算法这些算法不会改变它们所操作的容器中的元素。1.1 find 和 find_iffind(begin, end, value)&#xff1a;查找第一个等于 value 的元素&#xff0c;返回迭代器&#xff08;未找到返回 end&#xff09;。find_if(begin, end, predicate)&#xff1a;查找第一个满…

作者头像 李华