Java的几种创建实例方法的性能对比

  备注于 2019-08-10:

  以上是我对Lambda原理比较模糊时的测试,现在觉得唯一的用处在于对比第二篇得出在循环中使用Lambda会慢很多。

  实际运用的话,我建议看下一篇:Java的几种创建实例方法的性能对比(二) 

 

近来打算自己封装一个比较方便读写的Office Excel 工具类,前面已经写了一些,比较粗糙本就计划重构一下,刚好公司的电商APP后台原有的导出Excel实现出现了可怕的性能问题,600行的数据生成Excel工作簿居然需要50秒以上,客户端连接都被熔断了还没导出来,挺巧,那就一起解决吧。

在上一个版本里呢,我认为比较巧妙的地方在于用函数式编程的方式代替反射,很早以前了解了反射的一些底层后我就知道反射的性能很差,但一直没实际测试过各种调用场景的性能差距。

至于底层字节码、CPU指令这些我就不深究了,我还没到那个级别,那这次就来个简单的测试吧。

 

目标:创建Man对象。

方式:

① 直接引用 new Man();

② 使用反射

③ 使用内部类

④ 使用Lombda表达式

⑤ 使用Method Reference

在学习Java8新特性的时候,我所了解到的是Lombda表达式是内部类的一种简化书写方式,也就是语法糖,但两者间在运行时居然有比较明显的性能差距,让我不得不怀疑它底层到底是啥东西,时间精力有限先记着,有必要的时候再去啃openJDK吧。

还有就是Lombda和Method Reference从表现来看,底层应该是同一个东西,但IDEA既然分开两种内部类的写法推荐,那就分开对待吧。

测试时每种方式循环调用 1 亿次,每种方式连续计算两次时间,然后对比第二次运行的结果,直接run没有采用debug运行。

贴代码:

  1 package com.supalle.test;
  2 
  3 import lombok.AllArgsConstructor;
  4 import lombok.Builder;
  5 import lombok.Data;
  6 import lombok.NoArgsConstructor;
  7 
  8 import java.lang.reflect.Constructor;
  9 import java.lang.reflect.InvocationTargetException;
 10 import java.util.function.Supplier;
 11 
 12 /**
 13  * @描述:语法PK
 14  * @作者:Supalle
 15  * @时间:2019/7/26
 16  */
 17 public class SyntaxPKTest {
 18 
 19 
 20     /* 循环次数 */
 21     private final static int SIZE = 100000000;
 22 
 23     /* 有类如下 */
 24     @Data
 25     @Builder
 26     @NoArgsConstructor
 27     @AllArgsConstructor
 28     private static class Man {
 29         private String name;
 30         private int age;
 31     }
 32 
 33 
 34     /**
 35      * 使用 new Man();
 36      *
 37      * @return 运行耗时
 38      */
 39     public static long runWithNewConstructor() {
 40         long start = System.currentTimeMillis();
 41 
 42         for (int i = 0; i < SIZE; i++) {
 43             new SyntaxPKTest.Man();
 44         }
 45 
 46         return System.currentTimeMillis() - start;
 47     }
 48 
 49     /**
 50      * 使用反射
 51      *
 52      * @return 运行耗时
 53      */
 54     public static long runWithReflex() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
 55         Constructor<SyntaxPKTest.Man> constructor = SyntaxPKTest.Man.class.getConstructor();
 56         long start = System.currentTimeMillis();
 57 
 58         for (int i = 0; i < SIZE; i++) {
 59             constructor.newInstance();
 60         }
 61 
 62         return System.currentTimeMillis() - start;
 63     }
 64 
 65     /**
 66      * 使用内部类调用 new Man();
 67      *
 68      * @return 运行耗时
 69      */
 70     public static long runWithSubClass() {
 71         long start = System.currentTimeMillis();
 72 
 73         for (int i = 0; i < SIZE; i++) {
 74             new Supplier<SyntaxPKTest.Man>() {
 75                 @Override
 76                 public SyntaxPKTest.Man get() {
 77                     return new SyntaxPKTest.Man();
 78                 }
 79             }.get();
 80 
 81         }
 82 
 83         return System.currentTimeMillis() - start;
 84     }
 85 
 86     /**
 87      * 使用Lambda调用 new Man();
 88      *
 89      * @return 运行耗时
 90      */
 91     public static long runWithLambda() {
 92         long start = System.currentTimeMillis();
 93 
 94         for (int i = 0; i < SIZE; i++) {
 95             ((Supplier<SyntaxPKTest.Man>) () -> new SyntaxPKTest.Man()).get();
 96         }
 97 
 98         return System.currentTimeMillis() - start;
 99     }
100 
101 
102     /**
103      * 使用 MethodReference
104      *
105      * @return 运行耗时
106      */
107     public static long runWithMethodReference() {
108         long start = System.currentTimeMillis();
109 
110         for (int i = 0; i < SIZE; i++) {
111             ((Supplier<SyntaxPKTest.Man>) SyntaxPKTest.Man::new).get();
112         }
113 
114         return System.currentTimeMillis() - start;
115     }
116 
117     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
118 
119         // 测试前调用一下,加载Man字节码,尽量公平
120         SyntaxPKTest.Man man1 = new SyntaxPKTest.Man();
121         SyntaxPKTest.Man man2 = new SyntaxPKTest.Man("张三", 20);
122 
123         System.out.println("测试环境:CPU核心数 - " + Runtime.getRuntime().availableProcessors());
124 
125         System.out.println();
126 
127         // 这里的话对比再次调用的时间
128         System.out.println("首次使用 new Man()            耗时:" + runWithNewConstructor());
129         System.err.println("再次使用 new Man()            耗时:" + runWithNewConstructor());
130         System.out.println("首次使用反射                   耗时:" + runWithReflex());
131         System.err.println("再次使用反射                   耗时:" + runWithReflex());
132         System.out.println("首次使用内部类调用 new Man()    耗时:" + runWithSubClass());
133         System.err.println("再次使用内部类调用 new Man()    耗时:" + runWithSubClass());
134         System.out.println("首次使用Lambda调用 new Man()   耗时:" + runWithLambda());
135         System.err.println("再次使用Lambda调用 new Man()   耗时:" + runWithLambda());
136         System.out.println("首次使用 MethodReference      耗时:" + runWithMethodReference());
137         System.err.println("再次使用 MethodReference      耗时:" + runWithMethodReference());
138 
139 
140     }
141 
142 }

 

运行结果:

一:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man()            耗时:3
首次使用反射                   耗时:312
再次使用反射                   耗时:276
首次使用内部类调用 new Man()    耗时:6
再次使用内部类调用 new Man()    耗时:3
首次使用Lambda调用 new Man()   耗时:142
再次使用Lambda调用 new Man()   耗时:100
首次使用 MethodReference      耗时:86
再次使用 MethodReference      耗时:85

二:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man()            耗时:2
首次使用反射                   耗时:326
再次使用反射                   耗时:275
首次使用内部类调用 new Man()    耗时:6
再次使用内部类调用 new Man()    耗时:3
首次使用Lambda调用 new Man()   耗时:122
再次使用Lambda调用 new Man()   耗时:86
首次使用 MethodReference      耗时:102
再次使用 MethodReference      耗时:83

三:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man()            耗时:3
首次使用反射                   耗时:322
再次使用反射                   耗时:288
首次使用内部类调用 new Man()    耗时:7
再次使用内部类调用 new Man()    耗时:2
首次使用Lambda调用 new Man()   耗时:128
再次使用Lambda调用 new Man()   耗时:92
首次使用 MethodReference      耗时:97
再次使用 MethodReference      耗时:81

 

如果Lambda和MethodReference调换一下位置如下:

 1      System.out.println("首次使用 new Man()            耗时:" + runWithNewConstructor());
 2         System.err.println("再次使用 new Man()            耗时:" + runWithNewConstructor());
 3         System.out.println("首次使用反射                   耗时:" + runWithReflex());
 4         System.err.println("再次使用反射                   耗时:" + runWithReflex());
 5         System.out.println("首次使用内部类调用 new Man()    耗时:" + runWithSubClass());
 6         System.err.println("再次使用内部类调用 new Man()    耗时:" + runWithSubClass());
 7         System.out.println("首次使用 MethodReference      耗时:" + runWithMethodReference());
 8         System.err.println("再次使用 MethodReference      耗时:" + runWithMethodReference());
 9         System.out.println("首次使用Lambda调用 new Man()   耗时:" + runWithLambda());
10         System.err.println("再次使用Lambda调用 new Man()   耗时:" + runWithLambda());

一:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:6
再次使用 new Man()            耗时:2
首次使用反射                   耗时:351
再次使用反射                   耗时:270
首次使用内部类调用 new Man()    耗时:6
再次使用内部类调用 new Man()    耗时:3
首次使用 MethodReference      耗时:128
再次使用 MethodReference      耗时:97
首次使用Lambda调用 new Man()   耗时:82
再次使用Lambda调用 new Man()   耗时:74

二:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man()            耗时:3
首次使用反射                   耗时:318
再次使用反射                   耗时:297
首次使用内部类调用 new Man()    耗时:6
再次使用内部类调用 new Man()    耗时:2
首次使用 MethodReference      耗时:117
再次使用 MethodReference      耗时:100
首次使用Lambda调用 new Man()   耗时:91
再次使用Lambda调用 new Man()   耗时:79

三:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:6
再次使用 new Man()            耗时:3
首次使用反射                   耗时:319
再次使用反射                   耗时:277
首次使用内部类调用 new Man()    耗时:8
再次使用内部类调用 new Man()    耗时:3
首次使用 MethodReference      耗时:139
再次使用 MethodReference      耗时:85
首次使用Lambda调用 new Man()   耗时:103
再次使用Lambda调用 new Man()   耗时:84

 

 总结:

  ① 如果不需要足够的灵活性,直接使用 new 来构造一个对象,效率最高的。

    ② 反射确确实实是垫底,当然它也给我们提供了足够全面的、灵活的类操纵能力。

    ③ 使用内部类的方式,效率上和直接new 非常贴近,虽然看起来代码多一些,但是足够灵活。

      ④ Lambda和Method Reference效率其实很贴近,又是一起在Java8推出的,底层实现应该是一样的,在效率上比起反射好很多。

 

  上个版本中,我使用的Method Reference,下个版本还会继续使用Method Reference,因为接口方式和内部类一致,如果碰到某些对性能要求非常极致的使用场景,可以在使用时以内部类的方式替代Method Reference而不需要改变工具类的代码。

  

  备注于 2019-08-10:

  以上是我对Lambda原理比较模糊时的测试,现在觉得唯一的用处在于对比第二篇得出在循环中使用Lambda会慢很多。

  实际运用的话,我建议看下一篇:Java的几种创建实例方法的性能对比(二) 

 

posted @ 2019-07-26 11:31  Supalle  阅读(1113)  评论(0编辑  收藏  举报