备注于 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的几种创建实例方法的性能对比的更多相关文章

  1. Java的几种创建实例方法的性能对比(二)

    上一篇里对几种书写方式进行了简单的测试,得出了一些初步的结论.这次简单了解Lambda原理后,对测试做了一些调整,发现得到不一样的结果,而这个调整,明显更契合实际开发的场景. 暂时还没有亲自去验证,主 ...

  2. Java中两种实现多线程方式的对比分析

    本文转载自:http://www.linuxidc.com/Linux/2013-12/93690.htm#0-tsina-1-14812-397232819ff9a47a7b7e80a40613cf ...

  3. Go_18: Golang 中三种读取文件发放性能对比

    Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提 ...

  4. Golang 中三种读取文件发放性能对比

    Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提 ...

  5. 求斐波那契数列第n位的几种实现方式及性能对比(c#语言)

    在每一种编程语言里,斐波那契数列的计算方式都是一个经典的话题.它可能有很多种计算方式,例如:递归.迭代.数学公式.哪种算法最容易理解,哪种算法是性能最好的呢? 这里给大家分享一下我对它的研究和总结:下 ...

  6. java线程——三种创建线程的方式

    前言 线程,英文Thread.在java中,创建线程的方式有三种: 1.Thread 2.Runnable 3.Callable 在详细介绍下这几种方式之前,我们先来看下Thread类和Runnabl ...

  7. Java数组3种创建方式

    public static void main(String[] args){ /** * 1. 固定大小的空数组, 动态创建 */ String[] strArr1 = new String[3]; ...

  8. Delegate、Thread、Task、ThreadPool几种方式创建异步任务性能对比

    开始预测的结果是 Task>Delegate>ThreadPool>>Thread. (一)测试代码 static async Task<int> AsyncTas ...

  9. 几种流行Webservice框架性能对比

    1      摘要 开发webservice应用程序中离不开框架的支持,当open-open网站列举的就有30多种,这对于开发者如何选择带来一定的疑惑.性能Webservice的关键要素,不同的框架性 ...

随机推荐

  1. Spring Cloud Ribbon配置详解

    概述 有时候需要自定义Ribbon的配置和客户端超时配置. 自动化配置 /* 使用属性自定义功能区客户端 从版本1.2.0开始,Spring Cloud Netflix现在支持使用属性与Ribbon文 ...

  2. spring boot之security

    上一节的时候,我们打开了springboot的端点,有一些数据是非常敏感的,比如/shutdown. 这一节,我们要给一些敏感信息加上权限控制. spring boot本身的security模块就很好 ...

  3. 【练习题】proj2 字符串压缩

    输入一个字符串,输出简单的压缩 1)单字符串压缩 : 输入:ABBBCCD , 输出AB3C2D 2)多字符串压缩 输入:AABCABCD,输出A(ABC)2D 1)压缩单个字符 #include & ...

  4. Binary classification - 聊聊评价指标的那些事儿【回忆篇】

    在解决分类问题的时候,可以选择的评价指标简直不要太多.但基本可以分成两2大类,我们今分别来说道说道 基于一个概率阈值判断在该阈值下预测的准确率 衡量模型整体表现(在各个阈值下)的评价指标 在说指标之前 ...

  5. 【多处摘抄】Tomcat监视与调优

    文章摘抄大量内容,已附上摘抄地址,未找到最初博文作者,在此对原作者表述感谢:    最近调整了公司的Web容器,然后把项目转移到了idea,并且重新分了包,我以前很多重复的东西整合了一下,但是最近线下 ...

  6. 浅谈Invoke 和 BegionInvoke的用法

    很多人对Invoke和BeginInvoke理解不深刻,不知道该怎么应用,在这篇博文里将详细阐述Invoke和BeginInvoke的用法: 首先说下Invoke和BeginInvoke有两种用法: ...

  7. spring源码深度解析— IOC 之 属性填充

    doCreateBean() 主要用于完成 bean 的创建和初始化工作,我们可以将其分为四个过程: createBeanInstance() 实例化 bean populateBean() 属性填充 ...

  8. MySQL数据库设计与开发规范

    目录 1. 规范背景与目的 2. 设计规范 2.1. 数据库设计 2.1.1. 库名 2.1.2. 表结构 2.1.3. 列数据类型优化 2.1.4. 索引设计 2.1.5. 分库分表.分区表 2.1 ...

  9. windows container 踩坑记

    windows container 踩坑记 Intro 我们有一些服务是 dotnet framework 的,不能直接跑在 docker linux container 下面,最近一直在折腾把它部署 ...

  10. 扩展了一些String常用的方法.

    using System; using System.Diagnostics; using System.Security.Cryptography; using System.Text; using ...