Answers:
在您的例子我相信str
是不会使用的外while
循环,否则你就不会问这个问题,因为它声明的内部while
循环不会是一个选项,因为它不会编译。
所以,既然str
是不使用外循环,在尽可能小的范围str
是内 while循环。
所以,答案是着重那str
绝对应该被while循环内声明。不,如果,不,不,但。
可能违反此规则的唯一情况是,由于某种原因,每个时钟周期都必须从代码中挤出是至关重要的,在这种情况下,您可能需要考虑实例化外部作用域并重新使用而不是在内部范围的每次迭代中重新实例化它。但是,由于java中字符串的不可变性,因此这不适用于您的示例:str的新实例将始终在循环开始时创建,并且必须在循环结束时将其丢弃,因此无法在那里进行优化。
编辑:(将我的评论插入下面的答案中)
在任何情况下,正确的处理方式是正确编写所有代码,为产品建立性能要求,根据该要求评估最终产品,如果不满足要求,则进行优化。通常最终会发生的事情是,您找到了在几个地方提供一些不错的正式算法优化的方法,从而使我们的程序能够满足其性能要求,而不必遍历整个代码库并对其进行调整和修改。为了在这里和那里挤压时钟周期。
我比较了这两个(相似)示例的字节码:
让我们看一下1.示例:
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. example:
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规范的结果...
但是以最佳编码实践的名义,建议在尽可能小的范围内声明变量(在此示例中,该变量位于循环内部,因为这是使用变量的唯一位置)。
final
恋人:宣布str
为final
在inside
包装的情况下也没有什么区别=)
在最小范围内声明对象可提高可读性。
对于今天的编译器而言,性能并不重要。(在这种情况下)
从维护角度来看,第二个选项更好。
在尽可能小的范围内,在同一位置声明和初始化变量。
正如Donald Ervin Knuth所说:
“我们应该忘记效率低下的问题,大约有97%的时间是这样:过早的优化是万恶之源”
即,程序员出于性能考虑而影响一段代码设计的情况。这可能会导致一个设计,是不是干净,因为它可能已经或代码是不正确的,因为代码是复杂通过优化和程序员分心优化。
请跳至更新的答案...
对于那些关心性能的用户,请取出System.out并将循环限制为1个字节。下面使用Windows 7 Professional 64位和JDK-1.7.0_21,使用double(测试1/2)和String(3/4),以毫秒为单位给出经过时间。字节码(下面也为test1和test2给出)不相同。我懒得去测试可变和相对复杂的对象。
双
测试1耗时:2710毫秒
测试2耗时:2790毫秒
字符串(在测试中只需用字符串替换double即可)
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中获得更好的测试和详细结果
这与上面的代码不同。如果您只是编写一个虚拟循环,JVM将跳过它,因此至少您需要分配并返回一些东西。在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;
}
摘要:clarifiedBefore表示性能更好-确实很小-并且违反最小范围原则。JVM实际上应该为您执行此操作
解决此问题的一种方法是提供一个封装了while循环的可变范围:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
当外部作用域结束时,它们将自动取消引用。
在内部,变量可见的范围越小越好。
如果您不需要使用str
while循环之后(与范围有关),则第二个条件即
while(condition){
String str = calculateStr();
.....
}
更好,因为只有在condition
true 时才在堆栈上定义一个对象。即在需要时使用它
我认为,回答您问题的最佳资源是以下帖子:
根据我的理解,这件事将取决于语言。IIRC Java对此进行了优化,因此没有什么区别,但是JavaScript(例如)将在循环中每次都进行整个内存分配。特别是在Java中,我认为第二个在完成性能分析时会运行得更快。
正如许多人指出的那样,
String str;
while(condition){
str = calculateStr();
.....
}
是不是比这更好:
while(condition){
String str = calculateStr();
.....
}
因此,如果您不重复使用变量,请不要在其范围之外声明变量。
str
即使在下面的代码中执行后,该变量也将可用并在内存中保留一些空间。
String str;
while(condition){
str = calculateStr();
.....
}
该str
变量将不可用,并且还将释放str
以下代码中为该变量分配的内存。
while(condition){
String str = calculateStr();
.....
}
如果我们能肯定地遵循第二篇,将会减少我们的系统内存并提高性能。
确实,上述问题是编程问题。您想如何编写代码?您在哪里需要访问“ STR”?无需声明在本地用作全局变量的变量。我相信编程的基础。
对这个问题几乎每个人都警告:这是示例代码,在循环内,使用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);
}
}
结论:根据局部变量的大小,即使变量不是很大,差异也可能很大。
只是说有时候,循环的外部或内部确实很重要。
bigStuff[(int) (value % STUFF_SIZE)] = value;
(尝试输入2147483649L的值)
NullPointerException
如果您的calculateStr()
方法返回null,然后尝试在str上调用方法,则存在风险。
更普遍地,避免使用具有空值的变量。顺便说一句,它对于类属性更强。
NullPointerException.
如果尝试使用此代码,return str;
则编译错误为零。