网站首页 > java教程 正文
开发中我们常用到hashmap的遍历,随着jdk8的发布,又多了lambda和stream api的遍历方式,如何选择合适的遍历方式?我们可以仔细分析一下。
本文先从 HashMap 的遍历讲起,然后对性能进行测试、分析原理、安全性,来分析各种遍历方式的优点与不足,本文主要内容如下图所示:
HashMap的遍历
Hashmap的遍历方式可以分为四大类
- 迭代器方式遍历
- foreach方式遍历
- Streams Api方式遍历
- lambda表达式遍历
但每种类型下又有多种的表达方式,因此具体的遍历方式又可以分为以下 7 种:
- 使用迭代器EntrySet 的方式进行遍历;
- 使用迭代器KeySet 的方式进行遍历;
- 使用 foreach EntrySet 的方式进行遍历;
- 使用 foreach KeySet 的方式进行遍历;
- 使用 Streams Api 单线程的方式进行遍历;
- 使用 Streams Api 多线程的方式(parallel)进行遍历。
- 使用 lambda表达式的方式进行遍历;
接下来我们来看每种遍历的代码具体实现
1、迭代器EntrySet
上面程序输出结果为:
1->java 2->hadoop 3->spring 4->elasticsearch 5->flink
2、迭代器KeySet
以上程序输出结果为:
1->java 2->hadoop 3->spring 4->elasticsearch 5->flink
3、foreach EntrySet
以上程序输出结果为:
1->java 2->hadoop 3->spring 4->elasticsearch 5->flink
4、foreach KeySet
以上程序输出结果为:
1->java 2->hadoop 3->spring 4->elasticsearch 5->flink
5、Streams Api 单线程
以上程序输出结果为:
1->java 2->hadoop 3->spring 4->elasticsearch 5->flink
6、Streams Api 多线程
以上程序输出结果为:
4->elasticsearch 1->java 5->flink 2->hadoop 3->spring
7、lambda方式
以上程序输出结果为:
1->java 2->hadoop 3->spring 4->elasticsearch 5->flink
性能测试
下一步我们使用由Oracle官方提供的方法性能测试工具JMH(即Java Microbenchmark Harness),来测试一下这7个方法的性能。
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
@BenchmarkMode(Mode.Throughput) // 测试类型:吞吐量
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 4, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 4 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class HashmMapTest2 {
static Map<Integer, String> map = new HashMap() {{
// 添加数据
for (int i = 0; i < 10; i++) {
put(i, "v:" + i);
}
}};
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(HashmMapTest2.class.getSimpleName()) // 要导入的测试类
.output("D:/jmh-maptest.log") // 输出测试结果的文件
// .forks(1)
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void entrySet() {
// 遍历
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.print(entry.getKey()+entry.getValue());
}
}
@Benchmark
public void keySet() {
// 遍历
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.print(key+map.get(key));
}
}
@Benchmark
public void forEachEntrySet() {
// 遍历
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.print(entry.getKey()+entry.getValue());
}
}
@Benchmark
public void forEachKeySet() {
// 遍历
for (Integer key : map.keySet()) {
System.out.print(key+map.get(key));
}
}
@Benchmark
public void lambda() {
// 遍历
map.forEach((key, value) -> {
System.out.print(key+value);
});
}
@Benchmark
public void streamApi() {
// 单线程遍历
map.entrySet().stream().forEach((entry) -> {
System.out.print(entry.getKey()+entry.getValue());
});
}
@Benchmark
public void parallelStreamApi() {
// 多线程遍历
map.entrySet().parallelStream().forEach((entry) -> {
System.out.print(entry.getKey()+entry.getValue());
});
}
}
所有被测试的方法(添加@Benchmark注解方法),测试结果如下:
其中Benchmark指的是调用的类的方法,Mode指的模式是吞吐量测试,Cnt指的测试多少轮,Score指的平均执行时间, ± Error是表示Score的误差。从上面结果可以得出结论,在考虑上误差值的情况下,除了并行的parallelStream性能相比较高之外(结果乱序,多线程肯定比较快),其他方式的性能几乎没有明显差别。
性能原理分析
要理解性能测试的结果,我们可以通过反编译,查看代码的字节码查看原因,通过ideal查看字节码文件,内容如下。
public class HashMapTest {
public HashMapTest() {
}
@Test
public void iteratorEntrySet() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
Set<Entry<Integer, String>> entries = map.entrySet();
Iterator iterator = entries.iterator();
while(iterator.hasNext()) {
Entry<Integer, String> next = (Entry)iterator.next();
System.out.print(next.getKey() + "->" + (String)next.getValue());
System.out.print(" ");
}
}
@Test
public void iteratorKeySet() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
Set<Integer> integers = map.keySet();
Iterator iterator = integers.iterator();
while(iterator.hasNext()) {
Integer key = (Integer)iterator.next();
System.out.print(key + "->" + (String)map.get(key));
System.out.print(" ");
}
}
@Test
public void forEachEntrySet() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
Iterator var2 = map.entrySet().iterator();
while(var2.hasNext()) {
Entry<Integer, String> entry = (Entry)var2.next();
System.out.print(entry.getKey() + "->" + (String)entry.getValue() + " ");
}
}
@Test
public void forEachKeySet() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
Iterator var2 = map.keySet().iterator();
while(var2.hasNext()) {
Integer key = (Integer)var2.next();
System.out.print(key + "->" + (String)map.get(key) + " ");
}
}
@Test
public void lambda() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
map.forEach((k, v) -> {
System.out.print(k + "->" + v + " ");
});
}
@Test
public void singleStream() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
map.entrySet().stream().forEach((entry) -> {
System.out.print(entry.getKey() + "->" + (String)entry.getValue() + " ");
});
}
@Test
public void parallelStream() {
Map<Integer, String> map = new HashMap();
map.put(Integer.valueOf(1), "java");
map.put(Integer.valueOf(2), "hadoop");
map.put(Integer.valueOf(3), "spring");
map.put(Integer.valueOf(4), "elasticsearch");
map.put(Integer.valueOf(5), "flink");
map.entrySet().parallelStream().forEach((entry) -> {
System.out.print(entry.getKey() + "->" + (String)entry.getValue() + " ");
});
}
}
从结果可以看出,除了Streams 和Lambda 以外,通过迭代器和 for 循环的遍历EntrySet 最终生成的代码是一样的,如下所示:
而通过迭代器和 for 循环遍历的 KeySet 代码也是一样的,如下所示:
通过字节码可以看出,使用 EntrySet 和 KeySet 代码差别不是很大,并不像网上源码分析说的那样,KeySet 的get方法在获取Value 的时候需要进行遍历,性能上EntrySet 优于KeySet 。从测试结果看两种性能 几乎是相近的。
安全性测试
1、迭代器方式
以上程序输出结果为:
1->java删除:2
3->spring4->elasticsearch5->flink
测试总结:迭代器方式删除数据安全
2、for循环方式
以上程序输出结果为:
测试总结:for循环遍历方式删除数据不安全
3、lambda方式
以上程序输出结果为:
测试总结:lambda方式遍历删除数据不安全
4、stream 方式
以上程序运行结果为:
测试总结:streams方式遍历删除数据不安全
小结
我们不能在for循环遍历中使用时来删除map里面的数据,这是非安全的操作方式,但我们可以使用迭代器的 iterator.remove() 的方法来删除数据,这是安全的操作方式。同样的lambada和stream在使用的时候删除集合里面的数据是非安全的操作方式。但是我们可以在使用之前删除集合数据然后进行遍历。
总结
本文主要讲了hashmap的四大类遍历方式(迭代器,for循环、lambda、streams),以及具体的7种遍历方式代码,除了stream的并行遍历外,其他几种遍历方式在性能上没有明显的差别,但是从代码的简洁和优雅上来看,lambda和stream有先天的优势。除此之外,还从安全性的方面测试了四类遍历在运行时删除数据的安全性。除了迭代方式外,其他三类在循环遍历的时候,删除数据是非安全的。但是我们可以先在遍历之前删除数据,然后进行遍历,这个操作方式是安全的。
总体来说,本文提供了 7 种常见的遍历方式,肯定不是最全的,我是想给读者在使用 HashMap 时选择最合适的方式,然而选择哪一种写法,在性能、JDK 版本(是否1.8)、安全性以及优雅和可读性等方面来综合考虑。最后,欢迎各位在评论区补充并留言,写出你们的想法。
猜你喜欢
- 2024-09-22 张小飞的Java之路——第三十七章——Map
- 2024-09-22 JavaScript中的数组遍历forEach()与map()方法以及兼容写法
- 2024-09-22 golang(go语言) map如何按照插入的顺序遍历? #go语言
- 2024-09-22 关于Java8中Map的一些骚操作你会那些....
- 2024-09-22 Apachec工具commons-collections4遍历Map
- 2024-09-22 Map遍历的四种方法效率对比(遍历map的三种方式)
- 2024-09-22 Map的遍历方式(map遍历remove)
- 2024-09-22 Scala set和map(scala菜鸟教程)
- 2024-09-22 关于Java Map,你应该掌握哪8个问题?
- 2024-09-22 Java HashMap 遍历方式性能探讨(遍历hashmap的三种方式)
你 发表评论:
欢迎- 最近发表
-
- class版本不兼容错误原因分析(class更新)
- 甲骨文Oracle公司为Java的最新LTS版本做出改进
- 「版本发布」Minecraft Java开发版 1.19.4-pre1 发布
- java svn版本管理工具(svn软件版本管理)
- 我的世界1.8.10钻石在第几层(我的世界1.7.2钻石在哪层)
- Java开发高手必备:在电脑上轻松切换多个JDK版本
- 2022 年 Java 开发报告:Java 8 八年不到,开发者都在用什么?
- 开发java项目,选择哪个版本的JDK比较合适?
- Java版本选型终极指南:8 vs 17 vs 21特性对决!大龄程序员踩坑总结
- POI Excel导入(poi excel导入附件)
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)