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

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

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 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() {
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) () -> 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::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而不需要改变工具类的代码。

原文地址https://www.cnblogs.com/supalle/p/11249227.html

相关文章
|
5天前
|
XML Java 数据库连接
性能提升秘籍:如何高效使用Java连接池管理数据库连接
在Java应用中,数据库连接管理至关重要。随着访问量增加,频繁创建和关闭连接会影响性能。为此,Java连接池技术应运而生,如HikariCP。本文通过代码示例介绍如何引入HikariCP依赖、配置连接池参数及使用连接池高效管理数据库连接,提升系统性能。
27 5
|
10天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
50 4
|
21天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
42 17
|
15天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
29 2
|
19天前
|
Java 数据库连接 数据库
优化之路:Java连接池技术助力数据库性能飞跃
在Java应用开发中,数据库操作常成为性能瓶颈。频繁的数据库连接建立和断开增加了系统开销,导致性能下降。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接,显著减少连接开销,提升系统性能。文章详细介绍了连接池的优势、选择标准、使用方法及优化策略,帮助开发者实现数据库性能的飞跃。
25 4
|
16天前
|
Java 数据库连接 数据库
深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能
在Java应用开发中,数据库操作常成为性能瓶颈。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能。文章介绍了连接池的优势、选择和使用方法,以及优化配置的技巧。
16 1
|
23天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3
|
23天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
23天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
15 1
|
23天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1