查看原文
其他

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

CSDN 2020-12-18

The following article is from java金融 Author java金融

作者 | java金融
来源 | java金融(ID:java4299)头图 |  CSDN 下载自东方IC


引言


最近刷知乎的时候看到一个比较有意思的问题,变量声明在循环体内还是循环体外?这个问题有人认为应该定义循环体外,不应该定义在循环体内。很多java代码优化建议都有这么一条建议:循环内不要不断创建对象引用 例如:
for (int i = 1; i <= count; i++){ Object obj = new Object();}
这种做法会导致内存中有 count Object 对象引用存在,count 很大的话,就耗费内存了,建议为改为:
Object obj = null;for (int i = 0; i <= count; i++) { obj = new Object();}
这样的话,内存中只有一份 Object 对象引用,每次 new Object() 的时候, Object 对象引用指向不同的 Object 罢了,但是内存中只有一份,这样就大大节省了内存空间了。这条建议应该也出现过在很多公司的代码规范上了吧。下面我们就来分析下变量声明在循环体内和变量声明循环体外的情况。


效率对比


首先我们先来看看写在循环体内和询环体外的效率比对,测试代码如下:
/** * @author: 公众号【java金融】 * @Date: * @Description: */
@BenchmarkMode(Mode.AverageTime) // 测试完成时间@OutputTimeUnit(TimeUnit.NANOSECONDS)@Warmup(iterations = 2) // 预热 2 轮,每次 1s@Measurement(iterations = 5) // 测试 5 轮,每次 1s@Fork(1) // fork 1 个线程@State(Scope.Thread)public class ForEachBenchMark {

public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(ForEachBenchMark.class.getSimpleName()) .result("result.json") .resultFormat(ResultFormatType.JSON).build(); new Runner(opt).run(); }
@Param(value = {"10", "50", "100"}) private int length;

/** * 循环体外创建对象 * @param blackhole */ @Benchmark public void outsideLoop(Blackhole blackhole) { Object object = null; for (int i = 0; i < length; i++) { object = new Object(); blackhole.consume(object); }

}
/** * 循环体内创建对象 * @param blackhole */ @Benchmark public void insideLoop(Blackhole blackhole) { for (int i = 0; i < length; i++) { Object object = new Object(); blackhole.consume(object);
}
}}
测试结果如下:
Benchmark                     (length)  Mode  Cnt    Score    Error  Units
ForEachBenchMark.insideLoop         10  avgt    5   58.629 ±  8.857  ns/op
ForEachBenchMark.insideLoop         50  avgt    5  293.726 ±  1.856  ns/op
ForEachBenchMark.insideLoop        100  avgt    5  587.185 ± 40.424  ns/op
ForEachBenchMark.outsideLoop        10  avgt    5   59.563 ±  5.057  ns/op
ForEachBenchMark.outsideLoop        50  avgt    5  305.829 ± 27.476  ns/op
ForEachBenchMark.outsideLoop       100  avgt    5  584.853 ± 20.289  ns/op
我们可以发现不管在循环外创建对象和循环内创建对象时间几乎都是一样的。

字节码对比


下面我们准备两个测试类
public class InsideTest { public static int count = 100; public List<Object> insideLoop() { List<Object> list = new ArrayList<>(); int n = 0; for (; ; ) { if (n > count) { break; } Object o = new Object(); list.add(o); } Object b = 2; return list; }}public class OutsideTest { public static int count = 100;public List<Object> outsideLoop() { List<Object> list = new ArrayList<>(); Object o = null; int n = 0; for (; ; ) { if (n > count) { break; } o = new Object(); list.add(o); } Object b = 2; return list; }
这两个编译后字节码几乎一模一样,除了循环体外(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。
javac -g OutsideTest.javajavap -v OutsideTest.class
LocalVariableTable: Start Length Slot Name Signature 28 8 3 o Ljava/lang/Object; 0 46 0 this Lcom/workit/autoconfigure/autoconfigure/controller/InsideTest; 8 38 1 list Ljava/util/List; 10 36 2 n I 44 2 3 b Ljava/lang/Object;
LocalVariableTable: Start Length Slot Name Signature 0 49 0 this Lcom/workit/autoconfigure/autoconfigure/controller/OutsideTest; 8 41 1 list Ljava/util/List; 10 39 2 o Ljava/lang/Object; 12 37 3 n I 47 2 4 b Ljava/lang/Object;
这是比较极端的情况下有1个solt的差距,如果把上述的代码 Object b = 2;就不会存在 solt 复用了。

总结


整体看下来貌似内存和效率都差不多。从“「局部变量作用域最小化」”原则上来说,变量声明在循环体内更合适一点,这样代码的阅读性更好。
参考https://www.zhihu.com/question/31751468
更多精彩推荐

图灵奖得主 John E. Hopcroft 等 300 余位 AI 学者“穿越”回宋代开国际 AI 大会,这场面你见过吗?

蚂蚁上市员工人均一套大 House,阿里程序员身价和这匹配吗?

Robust.ai 获得 1500 万美元融资,嘴炮 Gary Marcus 也难逃真香定律

面向全场景的鸿蒙操作系统能有多安全?

阿里云资深技术专家易立:我对云原生软件架构的观察与思考

赠书 | 四大通证类型:价值创新的源头

点分享点点赞点在看

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存