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运行。
贴代码:
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而不需要改变工具类的代码。
备注于 2019-08-10:
以上是我对Lambda原理比较模糊时的测试,现在觉得唯一的用处在于对比第二篇得出在循环中使用Lambda会慢很多。
实际运用的话,我建议看下一篇:Java的几种创建实例方法的性能对比(二)
Java的几种创建实例方法的性能对比的更多相关文章
- Java的几种创建实例方法的性能对比(二)
上一篇里对几种书写方式进行了简单的测试,得出了一些初步的结论.这次简单了解Lambda原理后,对测试做了一些调整,发现得到不一样的结果,而这个调整,明显更契合实际开发的场景. 暂时还没有亲自去验证,主 ...
- Java中两种实现多线程方式的对比分析
本文转载自:http://www.linuxidc.com/Linux/2013-12/93690.htm#0-tsina-1-14812-397232819ff9a47a7b7e80a40613cf ...
- Go_18: Golang 中三种读取文件发放性能对比
Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提 ...
- Golang 中三种读取文件发放性能对比
Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提 ...
- 求斐波那契数列第n位的几种实现方式及性能对比(c#语言)
在每一种编程语言里,斐波那契数列的计算方式都是一个经典的话题.它可能有很多种计算方式,例如:递归.迭代.数学公式.哪种算法最容易理解,哪种算法是性能最好的呢? 这里给大家分享一下我对它的研究和总结:下 ...
- java线程——三种创建线程的方式
前言 线程,英文Thread.在java中,创建线程的方式有三种: 1.Thread 2.Runnable 3.Callable 在详细介绍下这几种方式之前,我们先来看下Thread类和Runnabl ...
- Java数组3种创建方式
public static void main(String[] args){ /** * 1. 固定大小的空数组, 动态创建 */ String[] strArr1 = new String[3]; ...
- Delegate、Thread、Task、ThreadPool几种方式创建异步任务性能对比
开始预测的结果是 Task>Delegate>ThreadPool>>Thread. (一)测试代码 static async Task<int> AsyncTas ...
- 几种流行Webservice框架性能对比
1 摘要 开发webservice应用程序中离不开框架的支持,当open-open网站列举的就有30多种,这对于开发者如何选择带来一定的疑惑.性能Webservice的关键要素,不同的框架性 ...
随机推荐
- Spring Cloud Ribbon配置详解
概述 有时候需要自定义Ribbon的配置和客户端超时配置. 自动化配置 /* 使用属性自定义功能区客户端 从版本1.2.0开始,Spring Cloud Netflix现在支持使用属性与Ribbon文 ...
- spring boot之security
上一节的时候,我们打开了springboot的端点,有一些数据是非常敏感的,比如/shutdown. 这一节,我们要给一些敏感信息加上权限控制. spring boot本身的security模块就很好 ...
- 【练习题】proj2 字符串压缩
输入一个字符串,输出简单的压缩 1)单字符串压缩 : 输入:ABBBCCD , 输出AB3C2D 2)多字符串压缩 输入:AABCABCD,输出A(ABC)2D 1)压缩单个字符 #include & ...
- Binary classification - 聊聊评价指标的那些事儿【回忆篇】
在解决分类问题的时候,可以选择的评价指标简直不要太多.但基本可以分成两2大类,我们今分别来说道说道 基于一个概率阈值判断在该阈值下预测的准确率 衡量模型整体表现(在各个阈值下)的评价指标 在说指标之前 ...
- 【多处摘抄】Tomcat监视与调优
文章摘抄大量内容,已附上摘抄地址,未找到最初博文作者,在此对原作者表述感谢: 最近调整了公司的Web容器,然后把项目转移到了idea,并且重新分了包,我以前很多重复的东西整合了一下,但是最近线下 ...
- 浅谈Invoke 和 BegionInvoke的用法
很多人对Invoke和BeginInvoke理解不深刻,不知道该怎么应用,在这篇博文里将详细阐述Invoke和BeginInvoke的用法: 首先说下Invoke和BeginInvoke有两种用法: ...
- spring源码深度解析— IOC 之 属性填充
doCreateBean() 主要用于完成 bean 的创建和初始化工作,我们可以将其分为四个过程: createBeanInstance() 实例化 bean populateBean() 属性填充 ...
- MySQL数据库设计与开发规范
目录 1. 规范背景与目的 2. 设计规范 2.1. 数据库设计 2.1.1. 库名 2.1.2. 表结构 2.1.3. 列数据类型优化 2.1.4. 索引设计 2.1.5. 分库分表.分区表 2.1 ...
- windows container 踩坑记
windows container 踩坑记 Intro 我们有一些服务是 dotnet framework 的,不能直接跑在 docker linux container 下面,最近一直在折腾把它部署 ...
- 扩展了一些String常用的方法.
using System; using System.Diagnostics; using System.Security.Cryptography; using System.Text; using ...