前进之路 发表于 2024-1-14 22:54:15

面试官:为什么阿里不推荐使用 keySet() 遍历 HashMap?太叼钻了吧。。

来源:https://juejin.cn/post/7295353579002396726
Part1 引言

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

[*]使用迭代器(Iterator)。
[*]使用 keySet() 获取键的集合,然后通过增强的 for 循环遍历键。
[*]使用 entrySet() 获取键值对的集合,然后通过增强的 for 循环遍历键值对。
[*]使用 Java 8+ 的 Lambda 表达式和流。
以上遍历方式的孰优孰劣,在《阿里巴巴开发手册》中写道:
https://img2023.cnblogs.com/other/1218593/202311/1218593-20231128104132295-666316890.jpg
这里推荐使用的是entrySet进行遍历,在Java8中推荐使用Map.forEach()。给出的理由是遍历次数上的不同。获取这份最新完整版手册,请在公众号 "Java核心技术" 回复"手册"即可免费获取。

[*]keySet遍历,需要经过两次遍历。
[*]entrySet遍历,只需要一次遍历。
其中keySet遍历了两次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。
其中后面一段话很好理解,但是前面这句话却有点绕,为什么转换成了Iterator遍历了一次?
我查阅了各个平台对HashMap的遍历,其中都没有或者原封不动的照搬上句话。(当然也可能是我没有查阅到靠谱的文章,欢迎指正)
推荐一个开源免费的 Spring Boot 实战项目:
https://github.com/javastacks/spring-boot-best-practice
Part2 keySet如何遍历了两次

我们首先写一段代码,使用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文件
https://img2023.cnblogs.com/other/1218593/202311/1218593-20231128104132639-1674536194.jpg
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()方法
1 iterator()

https://img2023.cnblogs.com/other/1218593/202311/1218593-20231128104132979-1721948000.jpg
发现是Set定义的一个接口。返回此集合中元素的迭代器
2 HashMap.KeySet#iterator()

我们查看HashMap中keySet类对该方法的实现。
https://img2023.cnblogs.com/other/1218593/202311/1218593-20231128104133348-657245060.jpg
https://img2023.cnblogs.com/other/1218593/202311/1218593-20231128104133727-1643642104.jpg
final class KeySet extends AbstractSet {    public final int size()               { return size; }    public final void clear()               { HashMap.this.clear(); }    public final Iterator 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 spliterator() {      return new KeySpliterator(HashMap.this, 0, -1, 0, 0);    }    public final void forEach(Consumer
页: [1]
查看完整版本: 面试官:为什么阿里不推荐使用 keySet() 遍历 HashMap?太叼钻了吧。。