场景
Java中使用JMH(Java Microbenchmark Harness 微基准测试框架)进行性能测试和优化:
Java中使用JMH(Java Microbenchmark Harness 微基准测试框架)进行性能测试和优化_java热点函数-CSDN博客
参考以上性能测试工具的使用。下面针对Java中对HashMap的7种遍历方式做性能测试。
注:
博客:
霸道流氓气质-CSDN博客
实现
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(){//创建并赋值HashMapMap<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();//执行测试}@Benchmarkpublic 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();}}@Benchmarkpublic void KeySetForEach(){Iterator<Integer> iterator = map.keySet().iterator();while(iterator.hasNext()){Integer key = iterator.next();String value = map.get(key);}}@Benchmarkpublic void ForEachEntrySet(){for (Map.Entry<Integer,String> entry:map.entrySet()) {Integer key = entry.getKey();String value = entry.getValue();}}@Benchmarkpublic void ForEachKeySet(){for(Integer key:map.keySet()){Integer k = key;String value = map.get(key);}}@Benchmarkpublic void LambdaForEach(){map.forEach((key,value)->{Integer k = key;String v = value;});}@Benchmarkpublic 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 集合。