在循环内部或外部声明variables
为什么以下工作正常?
String str; while (condition) { str = calculateStr(); ..... }
但是这个被认为是危险的/不正确的:
while (condition) { String str = calculateStr(); ..... }
是否有必要在循环之外声明variables?
局部variables的范围始终应尽可能小。
在你的例子中,我认为str
不在 while
循环之外,否则你不会问这个问题,因为在while
循环中声明它不会是一个选项,因为它不会被编译。
所以,因为str
不在循环外部使用, str
的最小可能范围在 while循环内。
所以,答案是强调 ,绝对应该在while循环内声明。 没有,如果没有,但不是。
这个规则可能被违反的唯一情况是,如果由于某种原因,每个时钟周期必须被挤出代码是非常重要的,在这种情况下,你可能要考虑在外部范围内实例化一些东西,而不是重用它在内部作用域的每次迭代中重新实例化它。 然而,这不适用于你的例子,由于在java中string的不变性:一个新的str实例总是会在你的循环开始时被创build,并且它将不得不在它的末尾被抛弃,所以在那里在那里优化是不可能的。
编辑:(注射我的评论下面的答案)
在任何情况下,正确的做法是正确编写所有代码,为您的产品build立一个性能要求,根据这个要求来衡量您的最终产品,如果不能满足要求,那就优化一下。 通常最终发生的事情是,你可以find方法来在几个地方提供一些很好的和forms化的algorithm优化,这使得我们的程序满足了它的性能要求,而不必遍历整个代码库,调整和破解为了挤压时钟周期在这里和那里。
我比较了这两个(类似)例子的字节码:
我们来看一个例子 :
package inside; public class Test { public static void main(String[] args) { while(true){ String str = String.valueOf(System.currentTimeMillis()); System.out.println(str); } } }
在javac Test.java
, javap -c Test
你会得到:
public class inside.Test extends java.lang.Object{ public inside.Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J 3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String; 6: astore_1 7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 10: aload_1 11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: goto 0 }
我们来看看2.例子 :
package outside; public class Test { public static void main(String[] args) { String str; while(true){ str = String.valueOf(System.currentTimeMillis()); System.out.println(str); } } }
在javac Test.java
, javap -c Test
你会得到:
public class outside.Test extends java.lang.Object{ public outside.Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J 3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String; 6: astore_1 7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 10: aload_1 11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: goto 0 }
观察显示这两个例子没有区别 。 这是JVM规范的结果…
但是,以最佳编码实践的名义,build议将variables声明在尽可能小的范围内(在这个例子中是在循环内部,因为这是唯一使用variables的地方)。
在最小范围内声明对象可以提高可读性 。
性能对今天的编译器无关紧要(在这种情况下)
从维护的angular度来看, 第二种select更好。
在尽可能最窄的范围内声明和初始化variables。
正如唐纳德·埃文·克努特所说:
“我们应该忘记小效率,大约97%的时间:不成熟的优化是万恶的根源”
即)程序员让性能考虑影响一段代码的devise的情况。 这可能会导致devise不够清晰 , 或者代码不正确,因为代码由于优化而变得复杂 ,程序员因为优化而分心。
如果你想使用外部循环也; 在外面宣布。 否则,第二版是好的。
在里面,variables的范围越小越好。
如果你不需要在while循环之后使用str
(范围相关的),那么第二个条件即
while(condition){ String str = calculateStr(); ..... }
是更好的,因为如果你只在condition
为真时在栈上定义一个对象。 即使用它, 如果你需要它
请跳到更新的答案…
对于那些关心性能的人来说,取出System.out并将循环限制为1个字节。 使用double(test 1/2)并使用String(3/4),经过的时间(以毫秒为单位)在Windows 7 Professional 64位和JDK-1.7.0_21中给出。 字节码(下面给出的test1和test2)也不相同。 我懒得testing可变和相对复杂的对象。
双
Test1采取:2710毫秒
Test2采取:2790 msecs
string(只需在testing中用stringreplacedouble)
Test3采取:1200毫秒
Test4采取:3000毫秒
编译并获取字节码
javac.exe LocalTest1.java javap.exe -c LocalTest1 > LocalTest1.bc public class LocalTest1 { public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); double test; for (double i = 0; i < 1000000000; i++) { test = i; } long finish = System.currentTimeMillis(); System.out.println("Test1 Took: " + (finish - start) + " msecs"); } } public class LocalTest2 { public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); for (double i = 0; i < 1000000000; i++) { double test = i; } long finish = System.currentTimeMillis(); System.out.println("Test1 Took: " + (finish - start) + " msecs"); } } Compiled from "LocalTest1.java" public class LocalTest1 { public LocalTest1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: lstore_1 4: dconst_0 5: dstore 5 7: dload 5 9: ldc2_w #3 // double 1.0E9d 12: dcmpg 13: ifge 28 16: dload 5 18: dstore_3 19: dload 5 21: dconst_1 22: dadd 23: dstore 5 25: goto 7 28: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 31: lstore 5 33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 36: new #6 // class java/lang/StringBuilder 39: dup 40: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 43: ldc #8 // String Test1 Took: 45: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 48: lload 5 50: lload_1 51: lsub 52: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 55: ldc #11 // String msecs 57: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 60: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 63: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: return } Compiled from "LocalTest2.java" public class LocalTest2 { public LocalTest2(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: lstore_1 4: dconst_0 5: dstore_3 6: dload_3 7: ldc2_w #3 // double 1.0E9d 10: dcmpg 11: ifge 24 14: dload_3 15: dstore 5 17: dload_3 18: dconst_1 19: dadd 20: dstore_3 21: goto 6 24: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 27: lstore_3 28: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 31: new #6 // class java/lang/StringBuilder 34: dup 35: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 38: ldc #8 // String Test1 Took: 40: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 43: lload_3 44: lload_1 45: lsub 46: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 49: ldc #11 // String msecs 51: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 54: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 57: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 60: return }
更新的答案
将性能与所有JVM优化进行比较并不容易。 但是,这是有点可能的。 在Google Caliper中进行更好的testing和详细的结果
- 博客上的一些细节: 你应该在一个循环内还是在循环之前声明一个variables?
- GitHub存储库: https : //github.com/gunduru/jvdt
- 双案例和100M循环的testing结果(以及是所有JVM详细信息): https : //microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4
- 在1,759.209纳秒前宣布
- DeclaredInside 2,242.308 ns
双重声明的部分testing代码
这与上面的代码不一样。 如果你只编写一个虚拟循环JVM跳过它,所以至less你需要分配和返回的东西。 这在Caliper文档中也是推荐的。
@Param int size; // Set automatically by framework, provided in the Main /** * Variable is declared inside the loop. * * @param reps * @return */ public double timeDeclaredInside(int reps) { /* Dummy variable needed to workaround smart JVM */ double dummy = 0; /* Test loop */ for (double i = 0; i <= size; i++) { /* Declaration and assignment */ double test = i; /* Dummy assignment to fake JVM */ if(i == size) { dummy = test; } } return dummy; } /** * Variable is declared before the loop. * * @param reps * @return */ public double timeDeclaredBefore(int reps) { /* Dummy variable needed to workaround smart JVM */ double dummy = 0; /* Actual test variable */ double test = 0; /* Test loop */ for (double i = 0; i <= size; i++) { /* Assignment */ test = i; /* Not actually needed here, but we need consistent performance results */ if(i == size) { dummy = test; } } return dummy; }
我认为回答你的问题最好的资源将是以下post:
在循环之前或循环中声明variables之间的区别?
根据我的理解,这件事情会依赖于语言。 IIRC Java对此进行了优化,因此没有任何区别,但是JavaScript(例如)会在循环中每次执行整个内存分配。特别是在Java中,我认为在完成性能分析时,第二个将运行得更快。
在wile循环之外声明String str允许在while循环内部和外部引用它。 在while循环中声明String str, 只允许在while循环中被引用。
解决这个问题的一个办法是提供一个封装while循环的variables作用域:
{ // all tmp loop variables here .... // .... String str; while(condition){ str = calculateStr(); ..... } }
当外部范围结束时,它们将自动取消引用。
variables应该被声明为尽可能靠近它们的位置。
它使RAII (资源获取初始化)更容易。
它保持variables的范围很紧。 这可以让优化器更好地工作。
根据谷歌Android开发指南,variables的范围应该是有限的。 请检查这个链接:
限制variables范围
上述问题确实是一个编程问题。 你想如何编程你的代码? 您需要在哪里访问“STR”? 没有使用声明在本地用作全局variables的variables。 我相信编程的基础。
即使在执行代码之后, str
variables也是可用的,并保留了一些空间。
String str; while(condition){ str = calculateStr(); ..... }
str
variables将不可用,并且内存将在下面的代码中为str
variables分配。
while(condition){ String str = calculateStr(); ..... }
如果我们跟着第二个肯定会减less我们的系统内存并提高性能。
在循环内声明限制了相应variables的范围。 这一切都取决于项目对variables范围的要求。
正如很多人所指出的那样,
String str; while(condition){ str = calculateStr(); ..... }
并不比这更好:
while(condition){ String str = calculateStr(); ..... }
所以如果你不重复使用它,就不要在它们的作用域之外声明variables。
这两个例子导致相同的事情。 不过,第一个提供的是在while循环之外使用str
variables; 第二个不是。
我认为对象的大小也很重要。 在我的一个项目中,我们已经声明并初始化了一个大的二维数组,这使得应用程序抛出了内存不足的exception。 我们将声明移出循环,并在每次迭代开始时清除数组。
如果您的calculateStr()
方法返回null ,然后尝试调用str上的方法,那么您有NullPointerException
的风险。
更一般地说,避免使用空值的variables。 顺便说一下,这对于class级属性来说更强。
几乎所有人都在这个问题上的警告:这里是示例代码,在循环内部,使用Java 7的计算机可以轻松200倍(而内存消耗也稍有不同)。 但这是关于分配而不是范围。
public class Test { private final static int STUFF_SIZE = 512; private final static long LOOP = 10000000l; private static class Foo { private long[] bigStuff = new long[STUFF_SIZE]; public Foo(long value) { setValue(value); } public void setValue(long value) { // Putting value in a random place. bigStuff[(int) (value % STUFF_SIZE)] = value; } public long getValue() { // Retrieving whatever value. return bigStuff[STUFF_SIZE / 2]; } } public static long test1() { long total = 0; for (long i = 0; i < LOOP; i++) { Foo foo = new Foo(i); total += foo.getValue(); } return total; } public static long test2() { long total = 0; Foo foo = new Foo(0); for (long i = 0; i < LOOP; i++) { foo.setValue(i); total += foo.getValue(); } return total; } public static void main(String[] args) { long start; start = System.currentTimeMillis(); test1(); System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); test2(); System.out.println(System.currentTimeMillis() - start); } }
结论:根据局部variables的大小,即使没有那么大的variables,差异也是巨大的。
只是有时候,在循环之外或之内是很重要的。