【Java】变量声明在循环体内还是循环体外你用哪一个?

开发 后端
最近刷知乎的时候看到一个比较有意思的问题,变量声明在循环体内还是循环体外?这个问题有人认为应该定义循环体外,不应该定义在循环体内。

[[349120]]

 引言

最近刷知乎的时候看到一个比较有意思的问题,变量声明在循环体内还是循环体外?这个问题有人认为应该定义循环体外,不应该定义在循环体内。很多java代码优化建议都有这么一条建议:循环内不要不断创建对象引用 例如:

  1. for (int i = 1; i <= count; i++){ 
  2.     Object obj = new Object(); 

这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:

  1. Object obj = null
  2. for (int i = 0; i <= count; i++) { 
  3.     obj = new Object(); 

这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。这条建议应该也出现过在很多公司的代码规范上了吧。下面我们就来分析下变量声明在循环体内和变量声明循环体外的情况。

效率对比

首先我们先来看看写在循环体内和询环体外的效率比对,测试代码如下:

  1. /** 
  2.  * @author: 公众号【java金融】 
  3.  * @Date:  
  4.  * @Description: 
  5.  */ 
  6.  
  7. @BenchmarkMode(Mode.AverageTime) // 测试完成时间 
  8. @OutputTimeUnit(TimeUnit.NANOSECONDS) 
  9. @Warmup(iterations = 2) // 预热 2 轮,每次 1s 
  10. @Measurement(iterations = 5) // 测试 5 轮,每次 1s 
  11. @Fork(1) // fork 1 个线程 
  12. @State(Scope.Thread) 
  13. public class ForEachBenchMark { 
  14.  
  15.  
  16.     public static void main(String[] args) throws RunnerException { 
  17.         Options opt = new OptionsBuilder() 
  18.                 .include(ForEachBenchMark.class.getSimpleName()) 
  19.                 .result("result.json"
  20.                 .resultFormat(ResultFormatType.JSON).build(); 
  21.         new Runner(opt).run(); 
  22.     } 
  23.  
  24.     @Param(value = {"10""50""100"}) 
  25.     private int length; 
  26.  
  27.  
  28.     /** 
  29.      * 循环体外创建对象 
  30.      * @param blackhole 
  31.      */ 
  32.     @Benchmark 
  33.     public void outsideLoop(Blackhole blackhole) { 
  34.         Object object = null
  35.         for (int i = 0; i < length; i++) { 
  36.             object = new Object(); 
  37.             blackhole.consume(object); 
  38.         } 
  39.  
  40.  
  41.     } 
  42.  
  43.     /** 
  44.      * 循环体内创建对象 
  45.      * @param blackhole 
  46.      */ 
  47.     @Benchmark 
  48.     public void insideLoop(Blackhole blackhole) { 
  49.         for (int i = 0; i < length; i++) { 
  50.             Object object = new Object(); 
  51.             blackhole.consume(object); 
  52.  
  53.         } 
  54.  
  55.     } 

测试结果如下:

  1. Benchmark                     (length)  Mode  Cnt    Score    Error  Units 
  2. ForEachBenchMark.insideLoop         10  avgt    5   58.629 ±  8.857  ns/op 
  3. ForEachBenchMark.insideLoop         50  avgt    5  293.726 ±  1.856  ns/op 
  4. ForEachBenchMark.insideLoop        100  avgt    5  587.185 ± 40.424  ns/op 
  5. ForEachBenchMark.outsideLoop        10  avgt    5   59.563 ±  5.057  ns/op 
  6. ForEachBenchMark.outsideLoop        50  avgt    5  305.829 ± 27.476  ns/op 
  7. ForEachBenchMark.outsideLoop       100  avgt    5  584.853 ± 20.289  ns/op 

我们可以发现不管在循环外创建对象和循环内创建对象时间几乎都是一样的。

字节码对比

下面我们准备两个测试类

  1. public class InsideTest { 
  2.     public static int count = 100; 
  3.     public List<Object> insideLoop() { 
  4.         List<Object> list = new ArrayList<>(); 
  5.         int n = 0; 
  6.         for (; ; ) { 
  7.             if (n > count) { 
  8.               break; 
  9.             } 
  10.             Object o = new Object(); 
  11.             list.add(o); 
  12.         } 
  13.         Object b = 2; 
  14.         return list; 
  15.     } 
  16. public class OutsideTest { 
  17.     public static int count = 100; 
  18. public List<Object> outsideLoop() { 
  19.         List<Object> list = new ArrayList<>(); 
  20.        Object o = null
  21.        int n = 0; 
  22.        for (; ; ) { 
  23.            if (n > count) { 
  24.                break; 
  25.            } 
  26.             o = new Object(); 
  27.             list.add(o); 
  28.         } 
  29.         Object b = 2; 
  30.         return list; 
  31.     } 

这两个编译后字节码几乎一模一样,除了循环体外(OutsideTest )常量池多了一个Object o = null变量还有的话就是LocalVariableTable有点区别,变量在循环体内的话公用了一个变量槽(o和b变量) outsideLoop在stack frame中定义了4个slot, 而intsideLoop只定义了3个slot 在outsideLoop中,变量o和b分别占用了不同的slot,在intsideloop中,变量o和b复用一个slot。所以outsideLoop的stack frame比intsideLoop多占用1个solt内存。执行以下命令就可以找到字节码中的LocalVariableTable。

  1. javac -g  OutsideTest.java 
  2. javap -v  OutsideTest.class 
  1. LocalVariableTable: 
  2.       Start  Length  Slot  Name   Signature 
  3.          28       8     3     o   Ljava/lang/Object; 
  4.           0      46     0  this   Lcom/workit/autoconfigure/autoconfigure/controller/InsideTest; 
  5.           8      38     1  list   Ljava/util/List; 
  6.          10      36     2     n   I 
  7.          44       2     3     b   Ljava/lang/Object; 
  1. LocalVariableTable: 
  2.        Start  Length  Slot  Name   Signature 
  3.            0      49     0  this   Lcom/workit/autoconfigure/autoconfigure/controller/OutsideTest; 
  4.            8      41     1  list   Ljava/util/List; 
  5.           10      39     2     o   Ljava/lang/Object; 
  6.           12      37     3     n   I 
  7.           47       2     4     b   Ljava/lang/Object; 

这是比较极端的情况下有1个solt的差距,如果把上述的代码 Object b = 2;就不会存在solt复用了。

总结

整体看下来貌似内存和效率都差不多。从“「局部变量作用域最小化」”原则上来说,变量声明在循环体内更合适一点,这样代码的阅读性更好。

本文转载自微信公众号「java金融」,可以通过以下二维码关注。转载本文请联系java金融公众号。

 

责任编辑:武晓燕 来源: java金融
相关推荐

2020-10-14 12:10:22

Javatry-catch代码

2021-11-19 14:44:17

树莓派Linux

2010-07-16 11:40:45

KVMXen

2021-11-15 09:53:16

STM32PSPMSP

2018-02-10 17:59:10

LinuxcURLwget

2019-04-29 15:27:57

网络故障工具

2020-06-17 15:00:27

FedoraUbuntuLinux

2023-10-08 11:53:29

2021-11-19 09:49:00

CC++语法糖

2022-10-08 09:03:16

JuliaPython机器学习

2018-10-18 14:26:45

云计算架构师管理

2018-06-22 15:52:50

云桌面

2011-11-08 08:59:51

云计算IaaSPaaS

2022-04-02 09:05:47

AlmaLinuxRocky LinuCentOS

2017-10-24 15:46:03

VMwareOpenStackvSphere

2018-07-18 13:29:38

云计算云平台企业

2018-11-29 09:45:03

Windows 10Windows版本

2018-08-12 23:15:04

iOS Android 操作系统

2017-07-05 11:38:29

互联网

2020-10-21 18:20:14

UbuntuPop!_OSLinux
点赞
收藏

51CTO技术栈公众号