Рубрики
Без рубрики

Объяснена конкатенация Строк Java

Этот учебник по Java поможет вам подробно понять, почему объединение строк является лучшей практикой Java.

Автор оригинала: Gábor László Hajba.

Я видел быструю подсказку здесь, в Codementor, и я хочу объяснить ее более подробно — чтобы вы знали, почему это лучшая практика.

Сам Быстрый Совет

Давайте посмотрим, какую лучшую практику я пересмотрю: “Если вы хотите объединить строки, не используйте”+”, а строковый конструктор, потому что каждый раз, когда вы используете”+”, создается новый объект, и в памяти у вас много неиспользуемых объектов”.

Вы можете прочитать об этой передовой практике в Кратком руководстве Суреша Атты здесь .

Ну, на самом деле, что он имеет в виду под “создается новый объект”?

Пример

Давайте начнем с примера из этого краткого совета. Я немного изменил его, чтобы тоже было немного контента:

String myString = "";
for(int i = 0; i < 1_000_000; i++) {
    myString += i;
}
System.out.println(myString);

Как вы можете видеть, я начинаю с пустой строки, а затем создаю цикл из миллиона чисел ( 1_000_000 это способ, которым вы можете написать 1000000 в Java 8-и на самом деле я нахожу его лучше и читабельнее).

Давайте скомпилируем и не запускаем приложение! Это требует времени, очень много времени! Но почему?

Как упоминает Суреш, этот код создаст миллион объектов впустую и уничтожит вашу память. Честно говоря, это создает около 2000000 объектов-намного больше, чем он думает.

Чтобы убедиться в этом, давайте посмотрим на байт-код сгенерированного файла .class , однако я не хочу, чтобы вы понимали, что здесь происходит, я включаю его для краткости:

public static void main(java.lang.String...);
    Code:
    0: ldc     #2   // String
    2: astore_1
    3: iconst_0
    4: istore_2
    5: iload_2
    6: ldc     #3   // int 1000000
    8: if_icmpge  36
   11: new     #4  // class java/lang/StringBuilder
   14: dup
   15: invokespecial #5  // Method java/lang/StringBuilder."":()V
   18: aload_1
   19: invokevirtual #6  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   22: iload_2
   23: invokevirtual #7  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   26: invokevirtual #8  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   29: astore_1
   30: iinc    2, 1
   33: goto    5
   36: getstatic  #9  // Field java/lang/System.out:Ljava/io/PrintStream;
   39: aload_1
   40: invokevirtual #10  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   43: return

Это байтовый код, который Java создает при компиляции приложения, вы можете посмотреть на него с помощью

javap -c YourClass.class

команда.

Интересная часть находится между строками 5 и 33 . Это цикл для в примере. Если вы посмотрите внимательно, вы увидите, что в теле цикла создается новый StringBuilder (строка 11 ) каждый раз в теле цикла с текущим содержимым myString (строка 19 ), а затем текущее значение i (строка 23 ) также добавляется в конструктор. Затем текущее значение StringBuilder преобразуется Строка и myString получает это присвоенное значение (строка 26 ).

И это не действие по исполнению. Создание объекта требует времени и ресурсов, поэтому приведенный выше пример кода занимает слишком много времени.

Решение с Быстрыми Подсказками

Теперь пришло время взглянуть на краткий совет, который является нашей лучшей практикой с тех пор:

StringBuilder sb = new StringBuilder();
for(int i = 0; i < 1_000_000; i++) {
    sb.append(i);
}
System.out.println(sb.toString());

Здесь вы создаете StringBuilder вне цикла for, добавляете значение я обращаюсь к строителю, и в конце вы распечатываете результат. Вы можете запустить это приложение, потому что оно завершается в кратчайшие сроки (единственное, что отнимает много времени,-это распечатать результат на консоли, потому что это операция ввода-вывода).

Теперь давайте также посмотрим на байт-код и сравним решения:

public static void main(java.lang.String...);
    Code:
       0: new           #2  // class java/lang/StringBuilder
       3: dup           
       4: invokespecial #3  // Method java/lang/StringBuilder."":()V
       7: astore_1      
       8: iconst_0      
       9: istore_2      
      10: iload_2       
      11: ldc           #4  // int 1000000
      13: if_icmpge     28
      16: aload_1       
      17: iload_2       
      18: invokevirtual #5  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      21: pop           
      22: iinc          2, 1
      25: goto          10
      28: getstatic     #6  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_1       
      32: invokevirtual #7  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      35: invokevirtual #8  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      38: return        
}

Цикл для здесь находится между строками 10 и 25 . Как вы можете видеть здесь, код добавляется только в онлайн-строку 4 создано StringBuilder без создания чего-либо нового. После завершения цикла содержимое StringBuilder преобразуется в строку и отображается на стандартном out .

Это означает, что мы убедились, что эта старая “лучшая практика” все еще верна, и мы знаем, почему следует придерживаться этой практики.

Вне цикла

Прочитав эту статью, я задаюсь вопросом, как насчет объединения строк вне циклов? Должны ли мы использовать там Строковый конструктор или использует + нормально?

Давайте посмотрим, что говорит нам код.

Прежде всего, это простой блок кода, который объединяет некоторые строки в предложение:

String greeting = "Hello" + " " + "World" + "!";
System.out.println(greeting);

Этот пример очень прост: мы объединяем эти четыре простые строки. Что происходит после компиляции?

public static void main(java.lang.String...);
    Code:
       0: ldc           #2                  // String Hello World!
       2: astore_1      
       3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: aload_1       
       7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: return

На самом деле никакой магии: код просто хранит значение “Привет, мир!” в виде одной строки. Итак, давайте назначим “Привет” и “Мир” переменным:

String hello = "Hello";
String world = "World";
String greeting = hello + " " + world + "!";
System.out.println(greeting);

Теперь это позволяет компилятору творить чудеса:

public static void main(java.lang.String...);
    Code:
       0: ldc           #2                  // String Hello
       2: astore_1      
       3: ldc           #3                  // String World
       5: astore_2      
       6: new           #4                  // class java/lang/StringBuilder
       9: dup           
      10: invokespecial #5                  // Method java/lang/StringBuilder."":()V
      13: aload_1       
      14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      17: ldc           #7                  // String  
      19: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: aload_2       
      23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #8                  // String !
      28: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      34: astore_3      
      35: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      38: aload_3       
      39: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      42: return

Как вы можете видеть, компилятор создает StringBuilder и добавляет четыре строительных блока, прежде чем преобразовать его в приветствие Строку. Поэтому в данном случае не имеет значения, создадим ли мы StringBuilder сами или объединим с + .

Как насчет того, чтобы быть немного более хитрым и делать то же самое, что мы делали в цикле, и объединять приветствие шаг за шагом?

String hello = "Hello";
String world = "World";

String greeting = hello;
greeting += " ";
greeting += world;
greeting += "!";
System.out.println(greeting);

Теперь я действительно противен — и компилятор не видит моего трюка, и мы сталкиваемся с той же проблемой, что и с циклом: мы получаем много StringBuilder созданных, которые потребляют память и время:

public static void main(java.lang.String...);
    Code:
       0: ldc           #2                  // String Hello
       2: astore_1      
       3: ldc           #3                  // String World
       5: astore_2      
       6: aload_1       
       7: astore_3      
       8: new           #4                  // class java/lang/StringBuilder
      11: dup           
      12: invokespecial #5                  // Method java/lang/StringBuilder."":()V
      15: aload_3       
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: ldc           #7                  // String  
      21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: astore_3      
      28: new           #4                  // class java/lang/StringBuilder
      31: dup           
      32: invokespecial #5                  // Method java/lang/StringBuilder."":()V
      35: aload_3       
      36: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      39: aload_2       
      40: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      46: astore_3      
      47: new           #4                  // class java/lang/StringBuilder
      50: dup           
      51: invokespecial #5                  // Method java/lang/StringBuilder."":()V
      54: aload_3       
      55: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      58: ldc           #9                  // String !
      60: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      63: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      66: astore_3      
      67: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      70: aload_3       
      71: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      74: return

Естественно, мы не видим никакого влияния на производительность этого небольшого приложения, но представьте себе более масштабный рабочий процесс, в котором вы добавляете значения в строку здесь и там, чтобы получить конечный результат (если вы спросите меня, я бы упомянул в качестве примера конструктор запросов, в котором вы можете создавать SQL-запрос на основе различных критериев).

Линии 8 , 28 и 47 создайте новый StringBuilder объекты так для каждого += идет строительство нового объекта.

Java 8 и присоединяйтесь

Java 8 представила новый строковый метод: join . Если мы этим занимаемся, давайте посмотрим, как join выполняет свою работу за кулисами.

String greeting = String.join("", "Hello", " ", "World", "!");
System.out.println(greeting);

Это опять же очень простой пример, где я использую пустую строку в качестве разделителя и добавляю символ пробела в качестве строки в список для объединения. Результат на консоли будет таким, как ожидалось Привет, мир! .

Теперь давайте взглянем на байт-код:

public static void main(java.lang.String...);
    Code:
       0: ldc           #2                  // String
       2: iconst_4      
       3: anewarray     #3                  // class java/lang/CharSequence
       6: dup           
       7: iconst_0      
       8: ldc           #4                  // String Hello
      10: aastore       
      11: dup           
      12: iconst_1      
      13: ldc           #5                  // String  
      15: aastore       
      16: dup           
      17: iconst_2      
      18: ldc           #6                  // String World
      20: aastore       
      21: dup           
      22: iconst_3      
      23: ldc           #7                  // String !
      25: aastore       
      26: invokestatic  #8                  // Method java/lang/String.join:(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
      29: astore_1      
      30: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      33: aload_1       
      34: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      37: return

Ну, это немного кода для такой короткой программы. Давайте проанализируем, что происходит!

На линии 0 он инициализирует строку без значения (это разделитель) в строке 8 , 13 , 18 и 23 он создает строки для строительных блоков приветствия. После этого значения объединяются в объект приветствие , который затем отображается.

Вывод

Некоторым лучшим практикам стоит доверять, однако вы должны знать, почему вы их используете. Если вы “просто делаете”, то подумайте о том, чтобы задать вопрос о причинах этого, потому что может случиться так, что это старая практика, и компилятор с некоторых пор изменил поведение вашего кода.

В этой статье я показал вам различные подходы к объединению строк и то, как они могут повлиять на производительность, если вы используете их чрезмерно. Кроме того, я доказал, что лучшая практика по-прежнему верна, и объяснил причины соответствующего быстрого совета.

Оригинал: “https://www.codementor.io/@ghajba/string-concatenation-revised-du1082cil”