Java的幾種建立實例方法的性能對比

近來打算本身封裝一個比較方便讀寫的Office Excel 工具類,前面已經寫了一些,比較粗糙本就計劃重構一下,恰好公司的電商APP後臺原有的導出Excel實現出現了可怕的性能問題,600行的數據生成Excel工做簿竟然須要50秒以上,客戶端鏈接都被熔斷了還沒導出來,挺巧,那就一塊兒解決吧。java

在上一個版本里呢,我認爲比較巧妙的地方在於用函數式編程的方式代替反射,很早之前瞭解了反射的一些底層後我就知道反射的性能不好,但一直沒實際測試過各類調用場景的性能差距。編程

至於底層字節碼、CPU指令這些我就不深究了,我還沒到那個級別,那此次就來個簡單的測試吧。ide

 

目標:建立Man對象。函數式編程

方式:函數

① 直接引用 new Man();工具

② 使用反射性能

③ 使用內部類學習

④ 使用Lombda表達式測試

⑤ 使用Method Referenceflex

在學習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而不須要改變工具類的代碼。

相關文章
相關標籤/搜索