先上结论:
- Java中HashMap有四种遍历方式,七种遍历方法
- 使用迭代器(Iterator)EntrySet 的方式进行遍历性能最高,最安全
(1)HashMap遍历
接下来我们来看每种遍历方式的具体实现代码。
package com.tset.three; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * @author :caizhengjie * @description:TODO * @date :2021/7/24 11:00 下午 */ public class TestMap { public static void main(String[] args) { // 创建map集合 Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "Alex"); map.put(2, "Lucy"); map.put(3, "Pony"); map.put(4, "Jerry"); map.put(5, "Jack"); /** * 循环遍历map集合的几种方式(四种遍历方式,七种遍历方法) * 可以分为四大类: * 1.迭代器(Iterator)方式遍历 * 2.For Each 方式遍历 * 3.Lambda 表达式遍历(JDK 1.8+) * 4.Streams API 遍历(JDK 1.8+) * * 但每种类型下又有不同的实现方式,因此具体的遍历方式又可以分为以下 7 种: * 1.使用迭代器(Iterator)EntrySet 的方式进行遍历; * 2.使用迭代器(Iterator)KeySet 的方式进行遍历; * 3.使用 For Each EntrySet 的方式进行遍历; * 4.使用 For Each KeySet 的方式进行遍历; * 5.使用 Lambda 表达式的方式进行遍历; * 6.使用 Streams API 单线程的方式进行遍历; * 7.使用 Streams API 多线程的方式进行遍历; */ /** * 1.使用迭代器(Iterator)EntrySet 的方式进行遍历; */ Iterator<Map.Entry<Integer,String>> iterator1 = map.entrySet().iterator(); while (iterator1.hasNext()){ Map.Entry<Integer,String> entry = iterator1.next(); System.out.println(entry.getKey() + " : " + entry.getValue()); } /** * 2.使用迭代器(Iterator)KeySet 的方式进行遍历; */ Iterator<Integer> iterator2 = map.keySet().iterator(); while (iterator2.hasNext()){ Integer key = iterator2.next(); System.out.println(key + " : " + map.get(key)); } /** * 3.使用 For Each EntrySet 的方式进行遍历; */ for (Map.Entry<Integer,String> entry : map.entrySet()){ System.out.println(entry.getKey() + " : " + entry.getValue()); } /** * 4.使用 For Each KeySet 的方式进行遍历; */ for (Integer key : map.keySet()){ System.out.println(key + " : " + map.get(key)); } /** * 5.使用 Lambda 表达式的方式进行遍历; */ map.forEach((key,value) -> { System.out.println(key + " : " + value); }); /** * 6.使用 Streams API 单线程的方式进行遍历; */ map.entrySet().stream().forEach((entry) -> { System.out.println(entry.getKey() + " : " + entry.getValue()); }); System.out.println("ss"); /** * 7.使用 Streams API 多线程的方式进行遍历; */ map.entrySet().parallelStream().forEach((entry) -> { System.out.println(entry.getKey() + " : " + entry.getValue()); }); } }
运行结果:
1 : Alex 2 : Lucy 3 : Pony 4 : Jerry 5 : Jack
(2)性能分析
前面的结论说EntrySet的方式性能最高,EntrySet之所以比KeySet的性能高是因为,KeySet在循环时使用了map.get(key),而map.get(key)相当于又遍历了一遍 Map 集合去查询key所对应的值。为什么要用“又”这个词?那是因为在使用迭代器或者 for 循环时,其实已经遍历了一遍 Map 集合了,因此再使用map.get(key)查询时,相当于遍历了两遍。
而EntrySet只遍历了一遍 Map 集合,之后通过代码“Entry<Integer, String> entry = iterator.next()”把对象的key和value值都放入到了Entry对象中,因此再获取key和value值时就无需再遍历 Map 集合,只需要从Entry对象中取值就可以了。
所以,EntrySet的性能比KeySet的性能高出了一倍,因为KeySet相当于循环了两遍 Map 集合,而EntrySet只循环了一遍。
(3)安全性分析
上面我们了解到使用KeySet的遍历方式的性能最高,下面继续从安全性来分析一下哪种遍历方式更安全,以删除map集合中key为1的元素为例,分别测试以下四种方式删除map集合中的元素,分为安全删除与不安全删除。
使用迭代器的方式进行删除
使用For Each的方式进行删除
使用 Lambda 表达式的方式进行删除
使用 Stream 的方式进行删除
(3.1)迭代器的方式进行删除
/** * 1.使用迭代器的方式进行删除(安全删除) */ Iterator<Map.Entry<Integer,String>> iterator1 = map.entrySet().iterator(); while (iterator1.hasNext()){ Map.Entry<Integer,String> entry = iterator1.next(); if (entry.getKey() == 1){ System.out.println("del " + " : " + entry.getKey() + " : " + entry.getValue()); iterator1.remove(); } else { System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue()); } }
运行结果:
del : 1 : Alex show : 2 : Lucy show : 3 : Pony show : 4 : Jerry show : 5 : Jack
结论:迭代器中循环删除数据安全
(3.2)For Each的方式进行删除
/** * 2.使用For Each的方式进行删除(不安全) */ for (Map.Entry<Integer,String> entry : map.entrySet()){ if (entry.getKey() == 1){ System.out.println("del " + " : " + entry.getKey() + " : " + entry.getValue()); map.remove(entry.getKey()); } else { System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue()); } }
运行结果:可以看出抛出的是并发修改异常
del : 1 : Alex Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445) at java.util.HashMap$EntryIterator.next(HashMap.java:1479) at java.util.HashMap$EntryIterator.next(HashMap.java:1477) at com.tset.three.TestMapDel.main(TestMapDel.java:44)
结论:For Each中循环删除数据不安全
(3.3)Lambda 表达式的方式进行删除
/** * 3.使用 Lambda 表达式的方式进行删除(不安全); */ map.forEach((key,value) -> { if (key == 1){ System.out.println("del " + " : " + key + " : " + value); map.remove(key); } else { System.out.println("show " + " : " + key + " : " + value); } });
运行结果:
del : 1 : Alex show : 2 : Lucy show : 3 : Pony show : 4 : Jerry show : 5 : Jack Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap.forEach(HashMap.java:1292) at com.tset.three.TestMapDel.main(TestMapDel.java:56)
测试结果:Lambda 循环中删除数据非安全
但是,Lambda 删除也有删除元素的正确方式
/** * 3.使用 Lambda 表达式的方式进行删除(安全); */ map.keySet().removeIf(key -> key == 1); map.forEach((key,value) -> { System.out.println(key + " : " + value); });
运行结果:
2 : Lucy 3 : Pony 4 : Jerry 5 : Jack
从上面的代码可以看出,可以先使用Lambda 的removeIf 删除多余的数据,再进行循环是一种正确操作集合的方式。
(3.4)Stream 的方式进行删除
/** * 4.使用 Stream 的方式进行删除(不安全); */ map.entrySet().stream().forEach((entry) -> { if (entry.getKey() == 1){ System.out.println("del " + " : " + entry.getKey() + " : " + entry.getValue()); map.remove(entry.getKey()); } else { System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue()); } });
运行结果:
del : 1 : Alex show : 2 : Lucy show : 3 : Pony show : 4 : Jerry show : 5 : Jack Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1704) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at com.tset.three.TestMapDel.main(TestMapDel.java:77)
测试结果:Stream 循环中删除数据非安全。
Stream 循环的正确方式:
/** * 4.使用 Streams 的方式进行删除(安全); */ map.entrySet().stream().filter(m -> 1 != m.getKey()).forEach((entry) -> { System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue()); });
运行结果:
show : 2 : Lucy show : 3 : Pony show : 4 : Jerry show : 5 : Jack
从上面的代码可以看出,可以使用Stream 中的filter 过滤掉无用的数据,再进行遍历也是一种安全的操作集合的方式。
(4)总结
安全删除map集合元素:
可以使用迭代器删除元素
可以使用Lambda 中removeIf方法删除元素
可以使用Stream中的filter 过滤掉无用的数据,再进行遍历
不安全删除map集合元素:
使用For Each循环删除元素会抛出并发修改异常
注意:不要在 foreach 循环里进行元素的 remove/add 操作。
总结HashMap 4 种遍历方式:迭代器、for、lambda、stream,以及具体的 7 种遍历方法,综合性能和安全性来看,我们应该尽量使用迭代器(Iterator)来遍历EntrySet的遍历方式来操作 Map 集合,这样就会既安全又高效了。