Java性能优化-HashMap遍历的7种方式及性能测试对比
场景
Java中使用JMH(Java Microbenchmark Harness 微基准测试框架)进行性能测试和优化:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/131723751
参考以上性能测试工具的使用。下面针对Java中对HashMap的7种遍历方式做性能测试。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
实现
Java中HashMap遍历的方式
HashMap遍历,从大的方向来说,可分为以下4类:
1、迭代器(Iterator)方式遍历
2、For Each方式遍历
3、Lambda表达式遍历(JDK 1.8+)
4、Streams API遍历(JDK1.8+)
但每种类型下又有不同的实现方式,因此具体的遍历⽅式又可以分为以下7种:
1. 使⽤迭代器(Iterator)EntrySet 的⽅式进⾏遍历;
public static void EntrySetForEach(){
//创建并赋值HashMap
Map<Integer,String> map = new HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
//遍历
Iterator<Map.Entry<Integer,String>> iterator =
map.entrySet().iterator();
while
(iterator.hasNext()){
Map.Entry<Integer,String> entry = iterator.next();
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
2. 使⽤迭代器(Iterator)KeySet 的⽅式进⾏遍历;
public static void KeySetForEach(){Map<Integer,String> map = new HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
Iterator<Integer> iterator = map.keySet().iterator();
while(iterator.hasNext()){
Integer key = iterator.next();
System.out.println(key);
System.out.println(map.get(key));
}
}
3. 使⽤ For Each EntrySet 的⽅式进⾏遍历;
public static void ForEachEntrySet(){
Map<Integer,String> map = new
HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
for (Map.Entry<Integer,String>
entry:map.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
4. 使⽤ For Each KeySet 的⽅式进⾏遍历;
public static void ForEachKeySet(){
Map<Integer,String> map = new
HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
for(Integer key:map.keySet()){
System.out.println(key);
System.out.println(map.get(key));
}
}
5. 使⽤ Lambda 表达式的⽅式进⾏遍历;
public static void LambdaForEach(){
Map<Integer,String> map = new
HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
map.forEach((key,value)->{
System.out.println(key);
System.out.println(value);
});
}
6. 使⽤ Streams API 单线程的⽅式进⾏遍历;
public static void StreamSingle(){
Map<Integer,String> map = new
HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
map.entrySet().stream().forEach((entry)->{
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
}
7. 使⽤ Streams API 多线程的⽅式进⾏遍历。
public static void StreamMul(){
Map<Integer,String> map = new
HashMap<>();
map.put(1,"公众号");
map.put(2,"霸道的程序猿");
map.put(3,"测试1");
map.put(4,"测试2");
map.put(5,"测试3");
map.entrySet().parallelStream().forEach((entry)->{
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
}
Java中7种遍历HashMap方式性能测试
编写如下测试类
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;
import java.util.HashMap;
import
java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
//测试完成时间
@BenchmarkMode(Mode.AverageTime)
//设置统计结果的时间单位
@OutputTimeUnit(TimeUnit.NANOSECONDS)
//预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。参数如下所示:
//
//iterations:预热的次数
//time:每次预热的时间
//timeUnit:时间的单位,默认秒
//batchSize:批处理大小,每次操作调用几次方法
//因为
JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,
//所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。
@Warmup(iterations = 2,time = 1,timeUnit =
TimeUnit.SECONDS)
//测试次数和时间,参数同上
@Measurement(iterations = 5,time = 1,timeUnit =
TimeUnit.SECONDS)
//fork一个线程,进行 fork 的次数,可用于类或者方法上。如果
fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。
@Fork(1)
//通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State
可以被继承使用,
//如果父类定义了该注解,子类则无需定义。由于 JMH
允许多线程同时执行测试,不同的选项含义如下:
//Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
//Scope.Group:同一个线程在同一个 group 里共享实例
//Scope.Thread:默认的 State,每个测试线程分配一个实例
@State(Scope.Thread)
public class
HashMapCycleTest {
static Map<Integer,String>
map = new HashMap(){{
for(int i =
0;i<100;i++){
put(i,"value"+i);
}
}};
public static void main(String[] args) throws
RunnerException {
//启动基准测试
Options options = new OptionsBuilder()
.include(HashMapCycleTest.class.getSimpleName())//要导入的测试类
.build();
new Runner(options).run();//执行测试
}
@Benchmark
public void
EntrySetForEach(){
//遍历
Iterator<Map.Entry<Integer,String>>
iterator = map.entrySet().iterator();
while
(iterator.hasNext()){
Map.Entry<Integer,String> entry = iterator.next();
Integer key = entry.getKey();
String value = entry.getValue();
}
}
@Benchmark
public void
KeySetForEach(){
Iterator<Integer> iterator =
map.keySet().iterator();
while(iterator.hasNext()){
Integer key =
iterator.next();
String value =
map.get(key);
}
}
@Benchmark
public void
ForEachEntrySet(){
for (Map.Entry<Integer,String>
entry:map.entrySet()) {
Integer key =
entry.getKey();
String value =
entry.getValue();
}
}
@Benchmark
public void
ForEachKeySet(){
for(Integer key:map.keySet()){
Integer k = key;
String value = map.get(key);
}
}
@Benchmark
public void
LambdaForEach(){
map.forEach((key,value)->{
Integer k = key;
String v = value;
});
}
@Benchmark
public void
StreamSingle(){
map.entrySet().stream().forEach((entry)->{
Integer k = entry.getKey();
String v = entry.getValue();
});
}
}
运行测试类
所有被添加了 @Benchmark 注解的⽅法都会被测试,因为 parallelStream 为多线程版本性能⼀定是最好的,所以就不参与测试了
测试结果
//Benchmark Mode Cnt
Score Error Units
//HashMapCycleTest.EntrySetForEach avgt 5 543.154 ±
36.365 ns/op
//HashMapCycleTest.ForEachEntrySet
avgt 5 533.511 ± 42.878 ns/op
//HashMapCycleTest.ForEachKeySet avgt 5 805.146 ±
118.210 ns/op
//HashMapCycleTest.KeySetForEach
avgt 5 785.286 ± 68.913 ns/op
//HashMapCycleTest.LambdaForEach avgt 5 429.048 ±
47.000 ns/op
//HashMapCycleTest.StreamSingle
avgt 5 501.687 ± 25.401 ns/op

结论:
从以上结果可以看出, lambda表达式和两个entrySet的性能相近,并且执⾏速度最快,接下来是 stream ,
然后是两个 keySet 。如果从性能⽅⾯考虑,我们应该尽量使⽤ lambda 或者是 entrySet 来遍历 Map 集合。