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的关键要素,不同的框架性 ...
随机推荐
- SQL数据库连接池与C#关键字return
SQL数据库连接池: 先前做的一个Sharepoint项目,在上线后的不久,最近一直出现间歇性访问缓慢问题Sharepoint特性问题,并分析了其数据库服务器,发现所耗内存已经达到了97%. 所以断定 ...
- Qt按ESC关闭模态对话框不触发closeEvent()问题解析(ESC默认调用的是reject()函数,所以必须覆盖这个函数才会有效果)good
事情是这样的:今天调试窗体,突然发现按ESC键居然跳过closeEvent()关闭了对话框!我的关闭判断都在closeEvent()里,这直接导致非正常关闭了正在进行的工作.先重建下场景: 调用处: ...
- flask(三)
1.cbv的用法 from flask import Flask,views app = Flask(__name__) class Login(views.MethodView ): def get ...
- python之数据分析pandas
做数据分析的同学大部分入门都是从excel开始的,excel也是微软office系列评价最高的一种工具. 但当数据量超过百万行的时候,excel就无能无力了,python第三方包pandas极大的扩展 ...
- Rails.cache相关知识
可能里面的一些知识已经不被大家使用了,但是作为学习,我想和大家分享一下个人关于Rails.cache的浅显的认识,望大家指教. 1.Rails.cache是什么 它是Rails中的缓存,拥有所有缓存的 ...
- 简单看看java之枚举
枚举类这个类用的比较少,对这个不怎么熟悉,最近看源码刚好可以好好了解一下,那么,枚举Enum是什么呢?在jdk中,Enum是一个抽象类下图所示,这就说明这个类是不能进行实例化的,那么我们应该怎么使用呢 ...
- Linux CentOS删除或重命名文件夹和文件的办法
Linux.CentOS操作系统下如何删除和重命名文件夹呢?办法如下: 一.Linux.CentOS下重命名文件和文件夹 mv:move 用移动文件命令就可以了,因为linux系统没有专门的重命名命令 ...
- 我以为我对Mysql索引很了解,直到我遇到了阿里的面试官
GitHub 4.8k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.8k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.8k Star 的 ...
- Python 爬虫从入门到进阶之路(十五)
之前的文章我们介绍了一下 Python 的 json 模块,本章我们就介绍一下之前根据 Xpath 模块做的爬取<糗事百科>的糗事进行丰富和完善. 在 Xpath 模块的爬取糗百的案例中我 ...
- HTTP 学习笔记03
通用信息头 Cache-Control : no-cache(不缓存当前请求) [*] Connection:close(返回当前请求后立即断开)[*] Date:...(HTTP消息产生的时间) P ...