共计 3129 个字符,预计需要花费 8 分钟才能阅读完成。
引言
最近刷知乎的时候看到一个比拟有意思的问题,变量申明在循环体内还是循环体外?这个问题有人认为应该定义循环体外,不应该定义在循环体内。很多 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.java
javap -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/questio…