20 ответов:
область действия локальных переменных всегда должна быть наименьшей из возможных.
в вашем примере я предполагаю
strи не за пределамиwhileцикл, иначе вы бы не задавали вопрос, потому что объявляя его внутриwhileцикл не будет опцией, так как он не будет компилироваться.так, с
strи не используется вне цикла, наименьшая возможная область дляstrи внутри пока петля.Итак, ответ категорически это
strабсолютно должны быть объявлены в цикле while. Никаких "если", нсра нет, никаких "но".единственный случай, когда это правило может быть нарушено, - это если по какой-то причине жизненно важно, чтобы каждый такт должен быть выжат из кода, и в этом случае вы можете рассмотреть возможность создания экземпляра чего-то во внешней области и повторного использования его вместо повторного создания его на каждой итерации внутреннего цикла. масштаб. Однако это не относится к вашему примеру, из-за неизменности строк в java: новый экземпляр str всегда будет создан в начале вашего цикла, и его придется выбросить в конце его, поэтому там нет возможности оптимизировать.
EDIT: (вводя мой комментарий ниже в ответ)
в любом случае, правильный способ сделать что-то, чтобы написать весь ваш код правильно, установить требования к производительности для вашего продукт, измерьте ваш конечный продукт против этого требования, и если оно не удовлетворяет его, то пойдите оптимизируйте вещи. И то, что обычно происходит, заключается в том, что вы находите способы обеспечить некоторые хорошие и формальные алгоритмические оптимизации всего в нескольких местах, которые делают нашу программу соответствующей ее требованиям к производительности вместо того, чтобы идти по всей вашей базе кода и настраивать и взламывать вещи, чтобы сжимать тактовые циклы здесь и там.
Я сравнил байтовый код этих двух (похожих) примеров:
давайте посмотрим на 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. пример:
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 технические характеристики...
но во имя лучшей практики кодирования рекомендуется объявить переменную в минимально возможный объем (в данном примере внутри цикла, так как это единственное место, где эта переменная используется).
объявление объектов в маленький объем улучшение читабельности.
производительность не имеет значения для современных компиляторов.(в этом случае)
С точки зрения технического обслуживания, 2-й лучше.
Объявить и инициализировать переменные в одном месте, в максимально узкой области.Как Дональд Эрвин Кнут сказал:
"мы должны забыть о мелких эффективности, скажем, около 97% времени: преждевременная оптимизация-корень всех зол"
т. е. ситуация, когда программист позволяет соображениям производительности влиять на конструкция кусок кода. Это может привести к конструкции, которая не так чист как это могло быть или код, который неверен, потому что код сложные на оптимизация и программист отвлекается на оптимизация.
Если вы хотите использовать
strвнешний looop также; объявить его снаружи. в противном случае, 2-я версия в порядке.
одним из решений этой проблемы может быть предоставление переменной области, инкапсулирующей цикл while:
{ // all tmp loop variables here .... // .... String str; while(condition){ str = calculateStr(); ..... } }они будут автоматически де-ссылки, когда внешняя область заканчивается.
Если вам не нужно использовать
strпосле цикла while (связанный с областью действия) затем второе условие, т. е.while(condition){ String str = calculateStr(); ..... }лучше, так как если вы определяете объект в стеке только если
condition- это правда. То есть использовать его если вам это нужно
пожалуйста, перейдите к обновленному ответу...
для тех, кто заботится о производительности вынимаем систему.выход и ограничение цикла до 1 байта. Использование double (test 1/2) и использование String (3/4) время, прошедшее в миллисекундах, приведено ниже в Windows 7 Professional 64 bit и JDK-1.7.0_21. Байт-коды (также приведенные ниже для test1 и test2) не совпадают. Я был слишком ленив, чтобы проверить с изменчивым и относительно сложным объекты.
двойной
Тест1 занял: 2710 МС
Test2 взял: 2790 МС
String (просто замените double на string в тестах)
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 }ОБНОВЛЕННЫЙ ОТВЕТ
Это действительно не легко сравнить производительность с Оптимизация виртуальной машины. Впрочем, это несколько возможно. Лучший тест и подробные результаты в Google Суппорт
- некоторые подробности в блоге:следует ли объявлять переменную внутри цикла или перед циклом?
- репозиторий GitHub:https://github.com/gunduru/jvdt
- результаты теста для двойного случая и петли 100М (и да все детали ДЖВМ): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4
- объявлено ранее 1,759. 209 НС
- DeclaredInside 2,242. 308 ns
частичный тестовый код для двойного объявления
это не совпадает с выше код. Если вы просто кодируете фиктивный цикл, JVM пропускает его, поэтому, по крайней мере, вам нужно назначить и вернуть что-то. Это также рекомендуется в суппорте документация.
@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; }резюме: declaredBefore указывает на лучшую производительность-действительно крошечный - и это против принципа наименьшего объема. JVM должен на самом деле сделать это для вас
Я думаю, что лучшим ресурсом для ответа на ваш вопрос будет следующий пост:
разница между объявлением переменных до или в цикле?
в моем понимании эта вещь будет зависеть от языка. IIRC Java оптимизирует это, поэтому нет никакой разницы, но JavaScript (например) будет делать все выделение памяти каждый раз в loop.In Java особенно я думаю, что второй будет работать быстрее, когда сделано профилирование.
объявление строки str вне цикла wile позволяет ссылаться на нее внутри и вне цикла while. Объявление строки str внутри цикла while позволяет ему только ссылки внутри цикла while.
переменные должны быть объявлены как можно ближе к месту их использования.
Это делает RAII (Сбор Ресурсов Является Инициализацией) легче.
Он держит объем переменной плотно. Это позволяет оптимизатору работать лучше.
согласно Руководству по разработке Google Android, переменная область должна быть ограничена. Пожалуйста, проверьте эту ссылку:
как указывали многие люди,
String str; while(condition){ str = calculateStr(); ..... }и не лучше, чем этот:
while(condition){ String str = calculateStr(); ..... }поэтому не объявляйте переменные вне их областей, если вы не используете его повторно...
объявление внутри цикла ограничивает область действия соответствующей переменной. Все зависит от требований проекта, от объема переменной.
действительно, вопрос, указанный выше, является проблемой программирования. Как бы вы хотели запрограммировать свой код? Где вам нужно получить доступ к 'STR'? Нет смысла объявлять переменную, которая используется локально как глобальная переменная. Основы программирования я считаю.
The
strпеременная будет доступна и зарезервирована некоторое пространство в памяти даже после выполнения ниже кода.String str; while(condition){ str = calculateStr(); ..... }The
strпеременная не будет доступна, а также память, которая была выделена дляstrпеременной в приведенном ниже коде.while(condition){ String str = calculateStr(); ..... }если мы следовали второй, конечно, это уменьшит нашу системную память и повысит производительность.
эти два примера приводят к одному и тому же. Тем не менее, первый предоставляет вам с помощью
strпеременная вне цикла while; второй-нет.
Я думаю, что размер объекта также имеет значение. В одном из моих проектов мы объявили и инициализировали большой двумерный массив, который заставлял приложение выбрасывать исключение из памяти. Вместо этого мы переместили объявление из цикла и очистили массив в начале каждой итерации.
предупреждение почти для всех в этом вопросе: Вот пример кода, где внутри цикла он может быть легко в 200 раз медленнее на моем компьютере с Java 7 (и потребление памяти также немного отличается). Но речь идет о выделении и не только объема.
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); } }вывод: в зависимости от размера локальной переменной, разница может быть огромной, даже с не очень большими переменными.
просто сказать, что иногда, снаружи или внутри цикла Имеет значение.
у вас есть риск
NullPointerExceptionЕслиcalculateStr()возвращает null а затем вы пытаетесь вызвать метод на str.в более общем случае, избегайте переменных с null значение. Это сильнее для атрибутов класса, кстати.

Comments