Part1引言

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。
publicclassTest{publicstaticvoidmain(String[]args){Map<String,String>map=newHashMap<>();map.put("k1","v1");map.put("k2","v2");map.put("k3","v3");for(Stringkey:map.keySet()){Stringvalue=map.get(key);System.out.println(key+":"+value);}}}
运行结果显而易见的是
k1:v1k2:v2k3:v3
两次遍历,第一次遍历所描述的是转为Iterator对象我们好像没有从代码中看见,我们看到的后面所描述的遍历,也就是遍历map,keySet()所返回的Set集合中的key,然后去HashMap中拿取value的。
Iterator对象呢?如何遍历转换为Iterator对象的呢?
首先我们这种遍历方式大家都应该知道是叫:增强for循环,for-each
这是一种Java的语法糖~。
我们可以通过反编译,或者直接通过Idea在class文件中查看对应的Class文件
publicclassTest{publicTest(){}publicstaticvoidmain(String[]args){Map<String,String>map=newHashMap();map.put("k1","v1");map.put("k2","v2");map.put("k3","v3");Iteratorvar2=map.keySet().iterator();while(var2.hasNext()){Stringkey=(String)var2.next();Stringvalue=(String)map.get(key);System.out.println(key+":"+value);}}}
和我们编写的是存在差异的,其中我们可以看到其中通过map.keySet().iterator()获取到了我们所需要看见的Iterator对象。
那么它又是怎么转换成的呢?为什么需要遍历呢?我们查看iterator()方法

1iterator()

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

2HashMap.KeySet#iterator()

我们查看HashMap中keySet类对该方法的实现。
finalclassKeySetextendsAbstractSet<K>{publicfinalintsize(){returnsize;}publicfinalvoidclear(){HashMap.this.clear();}publicfinalIterator<K>iterator(){returnnewKeyIterator();}publicfinalbooleancontains(Objecto){returncontainsKey(o);}publicfinalbooleanremove(Objectkey){returnremoveNode(hash(key),key,null,false,true)!=null;}publicfinalSpliterator<K>spliterator(){returnnewKeySpliterator<>(HashMap.this,0,-1,0,0);}publicfinalvoidforEach(Consumer<?superK>action){Node<K,V>[]tab;if(action==null)thrownewNullPointerException();if(size>0&&(tab=table)!=null){intmc=modCount;for(inti=0;i<tab.length;++i){for(Node<K,V>e=tab[i];e!=null;e=e.next)action.accept(e.key);}if(modCount!=mc)thrownewConcurrentModificationException();}}}
其中的iterator()方法返回的是一个KeyIterator对象,那么究竟是在哪里进行了遍历呢?我们接着往下看去。

3HashMap.KeyIterator

finalclassKeyIteratorextendsHashIteratorimplementsIterator<K>{publicfinalKnext(){returnnextNode().key;}}
这个类也很简单:
  1. 继承了HashIterator类。

  2. 实现了Iterator接口。

  3. 一个next()方法。

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

4HashMap.HashIterator

abstractclassHashIterator{Node<K,V>next;//nextentrytoreturnNode<K,V>current;//currententryintexpectedModCount;//forfast-failintindex;//currentslotHashIterator(){expectedModCount=modCount;Node<K,V>[]t=table;current=next=null;index=0;if(t!=null&&size>0){//advancetofirstentrydo{}while(index<t.length&&(next=t[index++])==null);}}publicfinalbooleanhasNext(){returnnext!=null;}finalNode<K,V>nextNode(){Node<K,V>[]t;Node<K,V>e=next;if(modCount!=expectedModCount)thrownewConcurrentModificationException();if(e==null)thrownewNoSuchElementException();if((next=(current=e).next)==null&&(t=table)!=null){do{}while(index<t.length&&(next=t[index++])==null);}returne;}publicfinalvoidremove(){Node<K,V>p=current;if(p==null)thrownewIllegalStateException();if(modCount!=expectedModCount)thrownewConcurrentModificationException();current=null;Kkey=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){//advancetofirstentrydo{}while(index<t.length&&(next=t[index++])==null);}}
KeyIterator是extendHashIterator对象的。这里涉及到了继承的相关概念,大家忘记的可以找相关的文章看看,或者我也可以写一篇~~dog。
例如两个类
publicclassFather{publicFather(){System.out.println("father");}}
publicclassSonextendsFather{publicstaticvoidmain(String[]args){Sonson=newSon();}}
创建Son对象的同时,会执行Father构造器。也就会打印出father这句话。
那么这个循环操作就是我们要找的循环操作了。

Part3总结

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

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

  3. KeyIterator对象extendHashIterator对象。

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

keySet->iterator()->KeyIterator->HashIterator
来源:juejin.cn/post/7295353579002396726
🔥磊哥私藏精品 热门推荐🔥
磊哥联合公司合伙人,一线大厂在职架构师耗时9个月联合打造2024年Java高级架构师课程》本课程对标外面3万左右的架构培训课程,分10个阶段,目前已经更新了181G视频,已经更新1000+个小时视频,一次购买,持续更新,无需2次付费

本篇文章来源于微信公众号: 架构师技术栈



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-08-02