Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

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

Supalle 2019-07-26 11:31:00 阅读数:12 评论数:0 点赞数:0 收藏数:0

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

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

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

 

目标:创建Man对象。

方式:

① 直接引用 new Man();

② 使用反射

③ 使用内部类

④ 使用Lombda表达式

⑤ 使用Method Reference

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

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

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

贴代码:

 package com.supalle.test;
 
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.function.Supplier;
 
 /**
  * @描述:语法PK
  * @作者:Supalle
  * @时间:2019/7/26
  */
 public class SyntaxPKTest {
 
 
     /* 循环次数 */
     private final static int SIZE = 100000000;
 
     /* 有类如下 */
     @Data
     @Builder
     @NoArgsConstructor
     @AllArgsConstructor
     private static class Man {
         private String name;
         private int age;
     }
 
 
     /**
      * 使用 new Man();
      *
      * @return 运行耗时
      */
     public static long runWithNewConstructor() {
         long start = System.currentTimeMillis();
 
         for (int i = 0; i < SIZE; i++) {
             new SyntaxPKTest.Man();
         }
 
         return System.currentTimeMillis() - start;
     }
 
     /**
      * 使用反射
      *
      * @return 运行耗时
      */
     public static long runWithReflex() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
         Constructor<SyntaxPKTest.Man> constructor = SyntaxPKTest.Man.class.getConstructor();
         long start = System.currentTimeMillis();
 
         for (int i = 0; i < SIZE; i++) {
             constructor.newInstance();
         }
 
         return System.currentTimeMillis() - start;
     }
 
     /**
      * 使用内部类调用 new Man();
      *
      * @return 运行耗时
      */
     public static long runWithSubClass() {
         long start = System.currentTimeMillis();
 
         for (int i = 0; i < SIZE; i++) {
             new Supplier<SyntaxPKTest.Man>() {
                 @Override
                 public SyntaxPKTest.Man get() {
                     return new SyntaxPKTest.Man();
                 }
             }.get();
 
         }
 
         return System.currentTimeMillis() - start;
     }
 
     /**
      * 使用Lambda调用 new Man();
      *
      * @return 运行耗时
      */
     public static long runWithLambda() {
         long start = System.currentTimeMillis();
 
         for (int i = 0; i < SIZE; i++) {
             ((Supplier<SyntaxPKTest.Man>) () -> new SyntaxPKTest.Man()).get();
         }
 
         return System.currentTimeMillis() - start;
     }
 
 
     /**
      * 使用 MethodReference
      *
      * @return 运行耗时
      */
     public static long runWithMethodReference() {
         long start = System.currentTimeMillis();
 
         for (int i = 0; i < SIZE; i++) {
             ((Supplier<SyntaxPKTest.Man>) SyntaxPKTest.Man::new).get();
         }
 
         return System.currentTimeMillis() - start;
     }
 
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
 
         // 测试前调用一下,加载Man字节码,尽量公平
         SyntaxPKTest.Man man1 = new SyntaxPKTest.Man();
         SyntaxPKTest.Man man2 = new SyntaxPKTest.Man("张三", 20);
 
         System.out.println("测试环境:CPU核心数 - " + Runtime.getRuntime().availableProcessors());
 
         System.out.println();
 
         // 这里的话对比再次调用的时间
         System.out.println("首次使用 new Man()            耗时:" + runWithNewConstructor());
         System.err.println("再次使用 new Man()            耗时:" + runWithNewConstructor());
         System.out.println("首次使用反射                   耗时:" + runWithReflex());
         System.err.println("再次使用反射                   耗时:" + runWithReflex());
         System.out.println("首次使用内部类调用 new Man()    耗时:" + runWithSubClass());
         System.err.println("再次使用内部类调用 new Man()    耗时:" + runWithSubClass());
         System.out.println("首次使用Lambda调用 new Man()   耗时:" + runWithLambda());
         System.err.println("再次使用Lambda调用 new Man()   耗时:" + runWithLambda());
         System.out.println("首次使用 MethodReference      耗时:" + runWithMethodReference());
         System.err.println("再次使用 MethodReference      耗时:" + runWithMethodReference());
 
 
     }
 
 }

 

运行结果:

一:

测试环境: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());
         System.err.println("再次使用 new Man()            耗时:" + runWithNewConstructor());
         System.out.println("首次使用反射                   耗时:" + runWithReflex());
         System.err.println("再次使用反射                   耗时:" + runWithReflex());
         System.out.println("首次使用内部类调用 new Man()    耗时:" + runWithSubClass());
         System.err.println("再次使用内部类调用 new Man()    耗时:" + runWithSubClass());
 7         System.out.println("首次使用 MethodReference      耗时:" + runWithMethodReference());
 8         System.err.println("再次使用 MethodReference      耗时:" + runWithMethodReference());
         System.out.println("首次使用Lambda调用 new Man()   耗时:" + runWithLambda());
         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而不需要改变工具类的代码。

 

版权声明
本文为[Supalle]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/supalle/p/11249227.html