备注于 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运行。

贴代码:

  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. private final static int SIZE = 100000000;
  21.  
  22. /* 有类如下 */
  23. @Data
  24. @Builder
  25. @NoArgsConstructor
  26. @AllArgsConstructor
  27. private static class Man {
  28. private String name;
  29. private int age;
  30. }
  31.  
  32. /**
  33. * 使用 new Man();
  34. *
  35. * @return 运行耗时
  36. */
  37. public static long runWithNewConstructor() {
  38. long start = System.currentTimeMillis();
  39.  
  40. for (int i = 0; i < SIZE; i++) {
  41. new SyntaxPKTest.Man();
  42. }
  43.  
  44. return System.currentTimeMillis() - start;
  45. }
  46.  
  47. /**
  48. * 使用反射
  49. *
  50. * @return 运行耗时
  51. */
  52. public static long runWithReflex() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  53. Constructor<SyntaxPKTest.Man> constructor = SyntaxPKTest.Man.class.getConstructor();
  54. long start = System.currentTimeMillis();
  55.  
  56. for (int i = 0; i < SIZE; i++) {
  57. constructor.newInstance();
  58. }
  59.  
  60. return System.currentTimeMillis() - start;
  61. }
  62.  
  63. /**
  64. * 使用内部类调用 new Man();
  65. *
  66. * @return 运行耗时
  67. */
  68. public static long runWithSubClass() {
  69. long start = System.currentTimeMillis();
  70.  
  71. for (int i = 0; i < SIZE; i++) {
  72. new Supplier<SyntaxPKTest.Man>() {
  73. @Override
  74. public SyntaxPKTest.Man get() {
  75. return new SyntaxPKTest.Man();
  76. }
  77. }.get();
  78.  
  79. }
  80.  
  81. return System.currentTimeMillis() - start;
  82. }
  83.  
  84. /**
  85. * 使用Lambda调用 new Man();
  86. *
  87. * @return 运行耗时
  88. */
  89. public static long runWithLambda() {
  90. long start = System.currentTimeMillis();
  91.  
  92. for (int i = 0; i < SIZE; i++) {
  93. ((Supplier<SyntaxPKTest.Man>) () -> new SyntaxPKTest.Man()).get();
  94. }
  95.  
  96. return System.currentTimeMillis() - start;
  97. }
  98.  
  99. /**
  100. * 使用 MethodReference
  101. *
  102. * @return 运行耗时
  103. */
  104. public static long runWithMethodReference() {
  105. long start = System.currentTimeMillis();
  106.  
  107. for (int i = 0; i < SIZE; i++) {
  108. ((Supplier<SyntaxPKTest.Man>) SyntaxPKTest.Man::new).get();
  109. }
  110.  
  111. return System.currentTimeMillis() - start;
  112. }
  113.  
  114. public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
  115.  
  116. // 测试前调用一下,加载Man字节码,尽量公平
  117. SyntaxPKTest.Man man1 = new SyntaxPKTest.Man();
  118. SyntaxPKTest.Man man2 = new SyntaxPKTest.Man("张三", 20);
  119.  
  120. System.out.println("测试环境:CPU核心数 - " + Runtime.getRuntime().availableProcessors());
  121.  
  122. System.out.println();
  123.  
  124. // 这里的话对比再次调用的时间
  125. System.out.println("首次使用 new Man() 耗时:" + runWithNewConstructor());
  126. System.err.println("再次使用 new Man() 耗时:" + runWithNewConstructor());
  127. System.out.println("首次使用反射 耗时:" + runWithReflex());
  128. System.err.println("再次使用反射 耗时:" + runWithReflex());
  129. System.out.println("首次使用内部类调用 new Man() 耗时:" + runWithSubClass());
  130. System.err.println("再次使用内部类调用 new Man() 耗时:" + runWithSubClass());
  131. System.out.println("首次使用Lambda调用 new Man() 耗时:" + runWithLambda());
  132. System.err.println("再次使用Lambda调用 new Man() 耗时:" + runWithLambda());
  133. System.out.println("首次使用 MethodReference 耗时:" + runWithMethodReference());
  134. System.err.println("再次使用 MethodReference 耗时:" + runWithMethodReference());
  135.  
  136. }
  137.  
  138. }

运行结果:

一:

  1. 测试环境:CPU核心数 - 8
  2.  
  3. 首次使用 new Man() 耗时:5
  4. 再次使用 new Man() 耗时:3
  5. 首次使用反射 耗时:312
  6. 再次使用反射 耗时:276
  7. 首次使用内部类调用 new Man() 耗时:6
  8. 再次使用内部类调用 new Man() 耗时:3
  9. 首次使用Lambda调用 new Man() 耗时:142
  10. 再次使用Lambda调用 new Man() 耗时:100
  11. 首次使用 MethodReference 耗时:86
  12. 再次使用 MethodReference 耗时:85

二:

  1. 测试环境:CPU核心数 - 8
  2.  
  3. 首次使用 new Man() 耗时:5
  4. 再次使用 new Man() 耗时:2
  5. 首次使用反射 耗时:326
  6. 再次使用反射 耗时:275
  7. 首次使用内部类调用 new Man() 耗时:6
  8. 再次使用内部类调用 new Man() 耗时:3
  9. 首次使用Lambda调用 new Man() 耗时:122
  10. 再次使用Lambda调用 new Man() 耗时:86
  11. 首次使用 MethodReference 耗时:102
  12. 再次使用 MethodReference 耗时:83

三:

  1. 测试环境:CPU核心数 - 8
  2.  
  3. 首次使用 new Man() 耗时:5
  4. 再次使用 new Man() 耗时:3
  5. 首次使用反射 耗时:322
  6. 再次使用反射 耗时:288
  7. 首次使用内部类调用 new Man() 耗时:7
  8. 再次使用内部类调用 new Man() 耗时:2
  9. 首次使用Lambda调用 new Man() 耗时:128
  10. 再次使用Lambda调用 new Man() 耗时:92
  11. 首次使用 MethodReference 耗时:97
  12. 再次使用 MethodReference 耗时:81

如果Lambda和MethodReference调换一下位置如下:

  1. 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. 7 System.out.println("首次使用 MethodReference 耗时:" + runWithMethodReference());
  8. 8 System.err.println("再次使用 MethodReference 耗时:" + runWithMethodReference());
  9. System.out.println("首次使用Lambda调用 new Man() 耗时:" + runWithLambda());
  10. System.err.println("再次使用Lambda调用 new Man() 耗时:" + runWithLambda());

一:

  1. 测试环境:CPU核心数 - 8
  2.  
  3. 首次使用 new Man() 耗时:6
  4. 再次使用 new Man() 耗时:2
  5. 首次使用反射 耗时:351
  6. 再次使用反射 耗时:270
  7. 首次使用内部类调用 new Man() 耗时:6
  8. 再次使用内部类调用 new Man() 耗时:3
  9. 首次使用 MethodReference 耗时:128
  10. 再次使用 MethodReference 耗时:97
  11. 首次使用Lambda调用 new Man() 耗时:82
  12. 再次使用Lambda调用 new Man() 耗时:74

二:

  1. 测试环境:CPU核心数 - 8
  2.  
  3. 首次使用 new Man() 耗时:5
  4. 再次使用 new Man() 耗时:3
  5. 首次使用反射 耗时:318
  6. 再次使用反射 耗时:297
  7. 首次使用内部类调用 new Man() 耗时:6
  8. 再次使用内部类调用 new Man() 耗时:2
  9. 首次使用 MethodReference 耗时:117
  10. 再次使用 MethodReference 耗时:100
  11. 首次使用Lambda调用 new Man() 耗时:91
  12. 再次使用Lambda调用 new Man() 耗时:79

三:

  1. 测试环境:CPU核心数 - 8
  2.  
  3. 首次使用 new Man() 耗时:6
  4. 再次使用 new Man() 耗时:3
  5. 首次使用反射 耗时:319
  6. 再次使用反射 耗时:277
  7. 首次使用内部类调用 new Man() 耗时:8
  8. 再次使用内部类调用 new Man() 耗时:3
  9. 首次使用 MethodReference 耗时:139
  10. 再次使用 MethodReference 耗时:85
  11. 首次使用Lambda调用 new Man() 耗时:103
  12. 再次使用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. SQL数据库连接池与C#关键字return

    SQL数据库连接池: 先前做的一个Sharepoint项目,在上线后的不久,最近一直出现间歇性访问缓慢问题Sharepoint特性问题,并分析了其数据库服务器,发现所耗内存已经达到了97%. 所以断定 ...

  2. Qt按ESC关闭模态对话框不触发closeEvent()问题解析(ESC默认调用的是reject()函数,所以必须覆盖这个函数才会有效果)good

    事情是这样的:今天调试窗体,突然发现按ESC键居然跳过closeEvent()关闭了对话框!我的关闭判断都在closeEvent()里,这直接导致非正常关闭了正在进行的工作.先重建下场景: 调用处: ...

  3. flask(三)

    1.cbv的用法 from flask import Flask,views app = Flask(__name__) class Login(views.MethodView ): def get ...

  4. python之数据分析pandas

    做数据分析的同学大部分入门都是从excel开始的,excel也是微软office系列评价最高的一种工具. 但当数据量超过百万行的时候,excel就无能无力了,python第三方包pandas极大的扩展 ...

  5. Rails.cache相关知识

    可能里面的一些知识已经不被大家使用了,但是作为学习,我想和大家分享一下个人关于Rails.cache的浅显的认识,望大家指教. 1.Rails.cache是什么 它是Rails中的缓存,拥有所有缓存的 ...

  6. 简单看看java之枚举

    枚举类这个类用的比较少,对这个不怎么熟悉,最近看源码刚好可以好好了解一下,那么,枚举Enum是什么呢?在jdk中,Enum是一个抽象类下图所示,这就说明这个类是不能进行实例化的,那么我们应该怎么使用呢 ...

  7. Linux CentOS删除或重命名文件夹和文件的办法

    Linux.CentOS操作系统下如何删除和重命名文件夹呢?办法如下: 一.Linux.CentOS下重命名文件和文件夹 mv:move 用移动文件命令就可以了,因为linux系统没有专门的重命名命令 ...

  8. 我以为我对Mysql索引很了解,直到我遇到了阿里的面试官

    GitHub 4.8k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.8k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.8k Star 的 ...

  9. Python 爬虫从入门到进阶之路(十五)

    之前的文章我们介绍了一下 Python 的 json 模块,本章我们就介绍一下之前根据 Xpath 模块做的爬取<糗事百科>的糗事进行丰富和完善. 在 Xpath 模块的爬取糗百的案例中我 ...

  10. HTTP 学习笔记03

    通用信息头 Cache-Control : no-cache(不缓存当前请求) [*] Connection:close(返回当前请求后立即断开)[*] Date:...(HTTP消息产生的时间) P ...