我今天在Java教程中阅读了以下代码。
StackTraceElement elements[] = e.getStackTrace();
for (int i = 0, n = elements.length; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
我可能会像新手那样编写代码。
StackTraceElement elements[] = e.getStackTrace();
for (int i = 0; i < elements.length; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
或者如下所示。
int n = elements.length;
for (int i = 0; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
我只是想问一下他们之间的区别是什么?还有其他更优秀的方法来优化Java中的for循环吗?
答案 0 :(得分:3)
没什么,它完全一样。除非您将element.length
保存在变量中并使用该变量。他们在同一时间也这样做。最后一个例子是最好的,因为它是最可读的imho。
答案 1 :(得分:3)
优化的黄金法则是,不要。
第二个黄金法则是,不是。
第三条黄金法则实际上是一个问题,你有没有测量过?
回答您的问题,请使用您团队中接受的标准:除非真的不同,否则您的代码不应该有任何不同。也就是说,在一个优秀的团队中,代码是相似和无聊的。任何一步都是一个巨大的闪烁的标志,说:“小心,这是规则的例外!” - 除非你想准确地说出来,否则请与其他人一起使用。
现在,代码方面,答案在编译器中:
import java.util.logging.*;
public class ForLoop {
private static Logger logger = Logger.getLogger("log");
public static void a(Throwable e) {
StackTraceElement elements[] = e.getStackTrace();
for (int i = 0, n = elements.length; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
public static void b(Throwable e) {
StackTraceElement elements[] = e.getStackTrace();
for (int i = 0; i < elements.length; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
public static void c(Throwable e) {
StackTraceElement elements[] = e.getStackTrace();
int n = elements.length;
for (int i = 0; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
}
编译成
alf-pro:Learning alf$ javap -p -c ForLoop.class
Compiled from "ForLoop.java"
public class ForLoop {
private static java.util.logging.Logger logger;
public ForLoop();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void a(java.lang.Throwable);
Code:
0: aload_0
1: invokevirtual #2 // Method java/lang/Throwable.getStackTrace:()[Ljava/lang/StackTraceElement;
4: astore_1
5: iconst_0
6: istore_2
7: aload_1
8: arraylength
9: istore_3
10: iload_2
11: iload_3
12: if_icmpge 36
15: getstatic #3 // Field logger:Ljava/util/logging/Logger;
18: getstatic #4 // Field java/util/logging/Level.WARNING:Ljava/util/logging/Level;
21: aload_1
22: iload_2
23: aaload
24: invokevirtual #5 // Method java/lang/StackTraceElement.getMethodName:()Ljava/lang/String;
27: invokevirtual #6 // Method java/util/logging/Logger.log:(Ljava/util/logging/Level;Ljava/lang/String;)V
30: iinc 2, 1
33: goto 10
36: return
public static void b(java.lang.Throwable);
Code:
0: aload_0
1: invokevirtual #2 // Method java/lang/Throwable.getStackTrace:()[Ljava/lang/StackTraceElement;
4: astore_1
5: iconst_0
6: istore_2
7: iload_2
8: aload_1
9: arraylength
10: if_icmpge 34
13: getstatic #3 // Field logger:Ljava/util/logging/Logger;
16: getstatic #4 // Field java/util/logging/Level.WARNING:Ljava/util/logging/Level;
19: aload_1
20: iload_2
21: aaload
22: invokevirtual #5 // Method java/lang/StackTraceElement.getMethodName:()Ljava/lang/String;
25: invokevirtual #6 // Method java/util/logging/Logger.log:(Ljava/util/logging/Level;Ljava/lang/String;)V
28: iinc 2, 1
31: goto 7
34: return
public static void c(java.lang.Throwable);
Code:
0: aload_0
1: invokevirtual #2 // Method java/lang/Throwable.getStackTrace:()[Ljava/lang/StackTraceElement;
4: astore_1
5: aload_1
6: arraylength
7: istore_2
8: iconst_0
9: istore_3
10: iload_3
11: iload_2
12: if_icmpge 36
15: getstatic #3 // Field logger:Ljava/util/logging/Logger;
18: getstatic #4 // Field java/util/logging/Level.WARNING:Ljava/util/logging/Level;
21: aload_1
22: iload_3
23: aaload
24: invokevirtual #5 // Method java/lang/StackTraceElement.getMethodName:()Ljava/lang/String;
27: invokevirtual #6 // Method java/util/logging/Logger.log:(Ljava/util/logging/Level;Ljava/lang/String;)V
30: iinc 3, 1
33: goto 10
36: return
static {};
Code:
0: ldc #7 // String log
2: invokestatic #8 // Method java/util/logging/Logger.getLogger:(Ljava/lang/String;)Ljava/util/logging/Logger;
5: putstatic #3 // Field logger:Ljava/util/logging/Logger;
8: return
}
正如您所看到的,a()
和c()
仅在variable assignment中有所不同,因此在JVM,JIT或非JIT中的行为几乎相同。 b()
略有不同:此处,arraylength
在循环内被调用。正如@thurstycrow指出的那样,它不一定是性能问题,但它可能是。
因此,当谈到现实生活中的表现时,你需要确保它值得考虑。检查堆栈跟踪的代码几乎永远不值得优化,因为您不希望一遍又一遍地抓取堆栈跟踪。此外,我认为日志记录比阵列长度检查更昂贵。
但是如果我们想象你有一个案例需要优化这个特定的代码,那就说你正在编写一个自定义监控工具来检查抛出的所有异常,你希望它非常缓慢而不是最终缓慢(你看,收集堆栈的痕迹很昂贵;导航它通常很便宜),你需要在野外测量它,因为JIT可以并且会考虑发生在周围此代码的所有内容好。
但这是一个无聊的答案,所以让我们仍然做一些基准测试,即使我们知道它没有多大意义 - 毕竟,我们可以,所以为什么不呢?您可以使用的一个很好的基准测试工具是JMH;你可以按照教程去玩它,或者只是抓住这个答案。
基准将是,
package org.sample;
import java.util.logging.*;
import java.util.*;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Scope;
@State(Scope.Thread)
public class ForLoop {
private static Logger logger = Logger.getLogger("log");
static {
logger.setLevel(Level.OFF);
}
private StackTraceElement elements[] = new Exception().getStackTrace();
@Benchmark
public void a0() {
for (int i = 0, n = elements.length; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
@Benchmark
public void a() {
for (int i = 0, n = elements.length; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
@Benchmark
public void b() {
for (int i = 0; i < elements.length; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
@Benchmark
public void c() {
int n = elements.length;
for (int i = 0; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
@Benchmark
public void d() {
for (StackTraceElement e: elements) {
logger.log(Level.WARNING, e.getMethodName());
}
}
@Benchmark
public void e() {
Arrays.stream(elements)
.forEach(item -> logger.log(Level.WARNING, item.getMethodName()));
}
}
(更多关于a0
之后;回答for-each和包含的流)。当我们运行它时,
alf-pro:test alf$ java -jar target/benchmarks.jar -wi 5 -i 5 -t 4 -f 1
# JMH 1.13 (released 41 days ago)
# VM version: JDK 1.8.0, VM 25.0-b70
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ForLoop.a
# Run progress: 0.00% complete, ETA 00:01:00
# Fork: 1 of 1
# Warmup Iteration 1: 139092694.098 ops/s
# Warmup Iteration 2: 130833602.261 ops/s
# Warmup Iteration 3: 121355640.666 ops/s
# Warmup Iteration 4: 114037414.954 ops/s
# Warmup Iteration 5: 110355992.809 ops/s
Iteration 1: 111331613.966 ops/s
Iteration 2: 111401698.736 ops/s
Iteration 3: 108193438.523 ops/s
Iteration 4: 105277721.260 ops/s
Iteration 5: 103549199.780 ops/s
Result "a":
107950734.453 ±(99.9%) 13602765.938 ops/s [Average]
(min, avg, max) = (103549199.780, 107950734.453, 111401698.736), stdev = 3532595.117
CI (99.9%): [94347968.515, 121553500.391] (assumes normal distribution)
# JMH 1.13 (released 41 days ago)
# VM version: JDK 1.8.0, VM 25.0-b70
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ForLoop.a0
# Run progress: 16.67% complete, ETA 00:00:51
# Fork: 1 of 1
# Warmup Iteration 1: 96143259.419 ops/s
# Warmup Iteration 2: 107561397.775 ops/s
# Warmup Iteration 3: 97488364.065 ops/s
# Warmup Iteration 4: 95880266.969 ops/s
# Warmup Iteration 5: 96943938.140 ops/s
Iteration 1: 95208831.922 ops/s
Iteration 2: 94012219.834 ops/s
Iteration 3: 95369199.325 ops/s
Iteration 4: 96090174.793 ops/s
Iteration 5: 90375159.036 ops/s
Result "a0":
94211116.982 ±(99.9%) 8743077.939 ops/s [Average]
(min, avg, max) = (90375159.036, 94211116.982, 96090174.793), stdev = 2270549.576
CI (99.9%): [85468039.043, 102954194.921] (assumes normal distribution)
# JMH 1.13 (released 41 days ago)
# VM version: JDK 1.8.0, VM 25.0-b70
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ForLoop.b
# Run progress: 33.33% complete, ETA 00:00:41
# Fork: 1 of 1
# Warmup Iteration 1: 58778298.331 ops/s
# Warmup Iteration 2: 66786552.916 ops/s
# Warmup Iteration 3: 67834850.418 ops/s
# Warmup Iteration 4: 69421299.220 ops/s
# Warmup Iteration 5: 72392517.533 ops/s
Iteration 1: 70791625.464 ops/s
Iteration 2: 70393808.080 ops/s
Iteration 3: 68931230.026 ops/s
Iteration 4: 67113234.799 ops/s
Iteration 5: 71628641.383 ops/s
Result "b":
69771707.950 ±(99.9%) 6847578.759 ops/s [Average]
(min, avg, max) = (67113234.799, 69771707.950, 71628641.383), stdev = 1778294.458
CI (99.9%): [62924129.192, 76619286.709] (assumes normal distribution)
# JMH 1.13 (released 41 days ago)
# VM version: JDK 1.8.0, VM 25.0-b70
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ForLoop.c
# Run progress: 50.00% complete, ETA 00:00:31
# Fork: 1 of 1
# Warmup Iteration 1: 87120233.617 ops/s
# Warmup Iteration 2: 88506588.802 ops/s
# Warmup Iteration 3: 82506886.728 ops/s
# Warmup Iteration 4: 75379852.092 ops/s
# Warmup Iteration 5: 83735974.951 ops/s
Iteration 1: 80107472.633 ops/s
Iteration 2: 85088925.886 ops/s
Iteration 3: 81051339.754 ops/s
Iteration 4: 85997882.597 ops/s
Iteration 5: 87092494.956 ops/s
Result "c":
83867623.165 ±(99.9%) 11946241.585 ops/s [Average]
(min, avg, max) = (80107472.633, 83867623.165, 87092494.956), stdev = 3102401.003
CI (99.9%): [71921381.580, 95813864.750] (assumes normal distribution)
# JMH 1.13 (released 41 days ago)
# VM version: JDK 1.8.0, VM 25.0-b70
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ForLoop.d
# Run progress: 66.67% complete, ETA 00:00:20
# Fork: 1 of 1
# Warmup Iteration 1: 90916967.864 ops/s
# Warmup Iteration 2: 91629586.405 ops/s
# Warmup Iteration 3: 107866944.554 ops/s
# Warmup Iteration 4: 102023453.435 ops/s
# Warmup Iteration 5: 111336773.130 ops/s
Iteration 1: 107770637.269 ops/s
Iteration 2: 107103283.175 ops/s
Iteration 3: 107545862.850 ops/s
Iteration 4: 113804266.277 ops/s
Iteration 5: 114628060.965 ops/s
Result "d":
110170422.107 ±(99.9%) 14295432.924 ops/s [Average]
(min, avg, max) = (107103283.175, 110170422.107, 114628060.965), stdev = 3712478.533
CI (99.9%): [95874989.183, 124465855.031] (assumes normal distribution)
# JMH 1.13 (released 41 days ago)
# VM version: JDK 1.8.0, VM 25.0-b70
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ForLoop.e
# Run progress: 83.33% complete, ETA 00:00:10
# Fork: 1 of 1
# Warmup Iteration 1: 33385239.816 ops/s
# Warmup Iteration 2: 38698292.771 ops/s
# Warmup Iteration 3: 38876327.513 ops/s
# Warmup Iteration 4: 37957299.742 ops/s
# Warmup Iteration 5: 39190117.656 ops/s
Iteration 1: 38286235.065 ops/s
Iteration 2: 39449652.416 ops/s
Iteration 3: 38541883.894 ops/s
Iteration 4: 40384962.473 ops/s
Iteration 5: 36975457.540 ops/s
Result "e":
38727638.278 ±(99.9%) 4934050.936 ops/s [Average]
(min, avg, max) = (36975457.540, 38727638.278, 40384962.473), stdev = 1281357.359
CI (99.9%): [33793587.342, 43661689.213] (assumes normal distribution)
# Run complete. Total time: 00:01:02
Benchmark Mode Cnt Score Error Units
ForLoop.a thrpt 5 107950734.453 ± 13602765.938 ops/s
ForLoop.a0 thrpt 5 94211116.982 ± 8743077.939 ops/s
ForLoop.b thrpt 5 69771707.950 ± 6847578.759 ops/s
ForLoop.c thrpt 5 83867623.165 ± 11946241.585 ops/s
ForLoop.d thrpt 5 110170422.107 ± 14295432.924 ops/s
ForLoop.e thrpt 5 38727638.278 ± 4934050.936 ops/s
我们可以看到所有三种方法都是如此之快,以至于在运行几次时,相同的方法会导致非常不同的测量结果。所以对于这个例子,我将停在这里。对于您的实际案例,请随意使用产生问题的实际代码进行选择。
PS。添加了for-each和流媒体解决方案。正如你所看到的,for-each几乎相同(虽然更具可读性);流明显慢。我将离开javap
examination这两个解决方案作为读者的练习。
答案 2 :(得分:2)
事实上,最好不要将数组长度存储到变量中,因为必须为每次迭代进行额外的数组边界检查。看看this video(从28:00开始)。它详细描述了JIT编译器为优化代码所做的工作。
答案 3 :(得分:1)
我会这样做:
for(StackTraceElement element : elements) {
logger.log(Level.WARNING, element.getMethodName());
}
答案 4 :(得分:1)
您也可以按照以下方式编写代码。
StackTraceElement elements[] = e.getStackTrace();
for(StackTraceElement element : elements) {
logger.log(Level.WARNING, element.getMethodName());
}
答案 5 :(得分:1)
与您的第一个版本的不同之处在于代码必须每次都取消引用elements
。从理论上讲,将值存储在变量中应该更有效率,但是这种操作非常便宜,实际上你几乎不会发现差异。
与第二个版本的区别主要在于您在for循环范围之外引入变量n
。也就是说,如果您想在下面重用n
作为变量名,那么只有在类型相同的情况下才能这样做(在这种情况下为int
)。
答案 6 :(得分:1)
实际上,这两个选项之间存在细微差别:
n
:在这里,您将重新计算elements.length final int n = elements.length;
for (int i = 0; i < n; i++)
次。现在,大多数情况下,集合的大小都会被缓存,但有时会在每次调用方法时重新计算大小,从而增加了时间复杂度。
for each
在这里,您只计算一次长度并使用它。将它作为最终决定也很重要,因为你不希望任何人改变它。
但是,最好的方法是使用for (StackTraceElement element : e.getStackTrace())
循环,如下所示:
p4 annotate
- 这将消除计数器的使用,看起来更好,因此更具可读性,并可能有一些性能提升。
答案 7 :(得分:1)
没有区别。
<强>情况#1 强>
您只是创建一个额外的变量并使用它。
<强>例#2 强>
每次循环运行elements.length
方法都将被计算。
<强>案#3 强>
这与案例1一样好。
答案 8 :(得分:0)
在这个循环中:
for (int i = 0; i < elements.length; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
对于每次迭代,您可以访问元素的长度变量。
在其他循环中:
int n = elements.length;
for (int i = 0; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
您在n变量中保存长度,但是对于每次迭代,您都可以访问n变量。
是一样的。
你可以写:
StackTraceElement elements[] = e.getStackTrace();
for(StackTraceElement element : elements) {
logger.log(Level.WARNING, element.getMethodName());
}
用于ArrayList的更易读的解决方案,例如:
arrayName.stream().forEach( item -> System.out.println(item.toString()));
此解决方案需要java 8。