Лучше ли использовать String.format вместо конкатенации строк в Java?

273

Есть ли ощутимая разница между использованием String.formatи объединением строк в Java?

Я склонен использовать, String.formatно иногда проскальзываю и использую сцепление. Мне было интересно, если один был лучше, чем другой.

То, как я это вижу, String.formatдает вам больше возможностей для «форматирования» строки; и конкатенация означает, что вам не нужно беспокоиться о случайном добавлении% s или об отсутствии.

String.format тоже короче.

Какой из них более читабелен, зависит от того, как работает ваша голова.

Омар Кохеджи
источник
Я думаю, что мы можем пойти с MessageFormat.format. Пожалуйста, смотрите ответ stackoverflow.com/a/56377112/1491414 для получения дополнительной информации.
Ганеса Виджаякумар

Ответы:

242

Я бы предположил, что это лучше использовать String.format(). Основная причина в том, что String.format()его легче локализовать с помощью текста, загруженного из файлов ресурсов, тогда как конкатенация не может быть локализована без создания нового исполняемого файла с различным кодом для каждого языка.

Если вы планируете, чтобы ваше приложение было локализуемым, вы должны также привыкнуть указывать позиции аргументов для ваших токенов формата:

"Hello %1$s the time is %2$t"

Затем его можно локализовать и поменять местами токены имени и времени, не требуя перекомпиляции исполняемого файла для учета другого порядка. С позициями аргументов вы также можете повторно использовать один и тот же аргумент, не передавая его в функцию дважды:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)
workmad3
источник
1
Можете ли вы указать мне некоторую документацию, в которой говорится о том, как работать с позициями / порядком аргументов в Java (т. Е. Как ссылаться на аргументы по их позиции)? Спасибо.
markvgti
13
Лучше поздно, чем никогда, случайная версия Java: docs.oracle.com/javase/1.5.0/docs/api/java/util/…
Аксель
174

О производительности:

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }
  long end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;

  start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = String.format("Hi %s; Hi to you %s",i, + i*2);
  }
  end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
}

Результаты синхронизации следующие:

  • Конкатенация = 265 миллисекунд
  • Формат = 4141 миллисекунда

Следовательно, конкатенация выполняется намного быстрее, чем String.format.

Icaro
источник
15
Они все плохая практика. Используйте StringBuilder.
Амир Раминфар
8
StringBuilder находится вне области действия (вопрос OP касался сравнения String.format со строкой конкатенации), но у вас есть данные о String Builder?
Икаро
108
@AmirRaminar: компилятор автоматически преобразует «+» в вызовы StringBuilder.
Мартин Шредер
40
@ MartinSchröder: если вы запустите, javap -c StringTest.classвы увидите, что компилятор автоматически преобразует «+» в StringBuilder, только если вы не находитесь в цикле. Если конкатенация выполняется в одну строку, это то же самое, что и использование «+», но если вы используете myString += "morechars";или myString += anotherString;несколько строк, вы заметите, что может быть создано более одного StringBuilder, поэтому использование «+» не всегда так эффективно как StringBuilder.
ccpizza
7
@Joffrey: я имел в виду, что циклы for +не конвертируются в, StringBuilder.append()а вместо этого new StringBuilder()происходит на каждой итерации.
ccpizza
39

Поскольку есть обсуждение производительности, я подумал, что я бы добавил в сравнение, которое включало StringBuilder. Это на самом деле быстрее, чем concat и, естественно, опция String.format.

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

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    log.info("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    log.info("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("; Hi to you ").append(i * 2);
    }

    end = System.currentTimeMillis();

    log.info("String Builder = " + ((end - start)) + " millisecond");
  • 2012-01-11 16: 30: 46,058 INFO [TestMain] - формат = 1416 миллисекунд
  • 2012-01-11 16: 30: 46,990 INFO [TestMain] - сцепление = 134 миллисекунды
  • 2012-01-11 16: 30: 46,313 ИНФОРМАЦИЯ [TestMain] - String Builder = 117 миллисекунд
TechTrip
источник
21
Тест StringBuilder не вызывает toString (), поэтому это несправедливое сравнение. Я подозреваю, что вы обнаружите, что в пределах ошибки измерения производительности конкатенации, если вы исправите эту ошибку.
Джейми Шарп
15
В тестах конкатенации и форматирования вы просили a String. Если честно, тест StringBuilder требует заключительного шага, который превращает содержимое StringBuilder в строку. Вы делаете это по телефону bldString.toString(). Я надеюсь, что это объясняет?
Джейми Шарп
4
Джейми Шарп совершенно прав. Вызов bldString.toString () примерно такой же, если не медленнее, чем конкатенация строк.
Akos Cz
3
String s = bldString.toString(); Тайминги были с конкатенацией и StringBuilder почти на одном уровне друг с другом: Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond я побежал их в цикле и в среднем каждый, чтобы получить хорошую репутацию: (предварительно Jvm оптимизацию, попробую 10000+ цикла , когда я получаю время)
TechTrip
3
Как вы, ребята, вообще знаете, выполняется ли код вообще? Переменные никогда не читаются и не используются, вы не можете быть уверены, что JIT не удалит этот код в первую очередь.
Алободзк
37

Одна из проблем .formatзаключается в том, что вы теряете безопасность статического типа. У вас может быть слишком мало аргументов для вашего формата, и у вас могут быть неправильные типы для спецификаторов формата - оба приводят к возникновению IllegalFormatException во время выполнения , так что вы можете в конечном итоге получить код, который нарушает работу.

Напротив, аргументы для +могут быть проверены компилятором.

История безопасности в(на котором formatмоделируется функция) долго и пугающе.

Мартин Шредер
источник
16
только для записи, современные IDE (например, IntelliJ) помогают в подсчете аргументов и сопоставлении типов
Ron Klein
2
Хороший вопрос по поводу компиляции, я рекомендую вам выполнить эти проверки через FindBugs (который может работать в IDE или через Maven во время сборки), обратите внимание, что это также проверит форматирование во всех ваших журналах! Это работает независимо от пользователей IDE
Кристоф Русси
20

Какой из них более читабелен, зависит от того, как работает ваша голова.

Вы получили свой ответ прямо там.

Это вопрос личного вкуса.

Полагаю, что сцепление строк незначительно быстрее, но это должно быть незначительным.

Тило
источник
3
Я согласен. Думать о различиях в производительности здесь в основном просто преждевременная оптимизация - в маловероятном случае, если профилирование покажет, что здесь есть проблема, тогда беспокойтесь об этом.
Джоник
3
Это действительно вопрос личного вкуса, если проект небольшой и никогда не предназначен для интернационализации в каком-либо значимом смысле. В противном случае String.format выигрывает у конкатенации во всех отношениях.
workmad3
4
Я не согласен. Неважно, насколько велик проект, вы вряд ли сможете локализовать каждую строку, которая когда-либо была в нем построена. Другими словами, это зависит от ситуации (для чего используются строки).
Джоник
Я не могу себе представить, как кто-либо мог бы считать, что «String.format («% s% s », a, b)» более читабелен, чем «a + b», и учитывая разницу в скорости на порядок, что ответ кажется мне ясным (в ситуациях, которые не требуют локализации, таких как отладка или большинство операторов регистрации).
BobDoolittle
16

Вот тест с несколькими размерами выборки в миллисекундах.

public class Time {

public static String sysFile = "/sys/class/camera/rear/rear_flash";
public static String cmdString = "echo %s > " + sysFile;

public static void main(String[] args) {

  int i = 1;
  for(int run=1; run <= 12; run++){
      for(int test =1; test <= 2 ; test++){
        System.out.println(
                String.format("\nTEST: %s, RUN: %s, Iterations: %s",run,test,i));
        test(run, i);
      }
      System.out.println("\n____________________________");
      i = i*3;
  }
}

public static void test(int run, int iterations){

      long start = System.nanoTime();
      for( int i=0;i<iterations; i++){
          String s = "echo " + i + " > "+ sysFile;
      }
      long t = System.nanoTime() - start;   
      String r = String.format("  %-13s =%10d %s", "Concatenation",t,"nanosecond");
      System.out.println(r) ;


     start = System.nanoTime();       
     for( int i=0;i<iterations; i++){
         String s =  String.format(cmdString, i);
     }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "Format",t,"nanosecond");
     System.out.println(r);

      start = System.nanoTime();          
      for( int i=0;i<iterations; i++){
          StringBuilder b = new StringBuilder("echo ");
          b.append(i).append(" > ").append(sysFile);
          String s = b.toString();
      }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "StringBuilder",t,"nanosecond");
     System.out.println(r);
}

}

TEST: 1, RUN: 1, Iterations: 1
  Concatenation =     14911 nanosecond
  Format        =     45026 nanosecond
  StringBuilder =      3509 nanosecond

TEST: 1, RUN: 2, Iterations: 1
  Concatenation =      3509 nanosecond
  Format        =     38594 nanosecond
  StringBuilder =      3509 nanosecond

____________________________

TEST: 2, RUN: 1, Iterations: 3
  Concatenation =      8479 nanosecond
  Format        =     94438 nanosecond
  StringBuilder =      5263 nanosecond

TEST: 2, RUN: 2, Iterations: 3
  Concatenation =      4970 nanosecond
  Format        =     92976 nanosecond
  StringBuilder =      5848 nanosecond

____________________________

TEST: 3, RUN: 1, Iterations: 9
  Concatenation =     11403 nanosecond
  Format        =    287115 nanosecond
  StringBuilder =     14326 nanosecond

TEST: 3, RUN: 2, Iterations: 9
  Concatenation =     12280 nanosecond
  Format        =    209051 nanosecond
  StringBuilder =     11818 nanosecond

____________________________

TEST: 5, RUN: 1, Iterations: 81
  Concatenation =     54383 nanosecond
  Format        =   1503113 nanosecond
  StringBuilder =     40056 nanosecond

TEST: 5, RUN: 2, Iterations: 81
  Concatenation =     44149 nanosecond
  Format        =   1264241 nanosecond
  StringBuilder =     34208 nanosecond

____________________________

TEST: 6, RUN: 1, Iterations: 243
  Concatenation =     76018 nanosecond
  Format        =   3210891 nanosecond
  StringBuilder =     76603 nanosecond

TEST: 6, RUN: 2, Iterations: 243
  Concatenation =     91222 nanosecond
  Format        =   2716773 nanosecond
  StringBuilder =     73972 nanosecond

____________________________

TEST: 8, RUN: 1, Iterations: 2187
  Concatenation =    527450 nanosecond
  Format        =  10291108 nanosecond
  StringBuilder =    885027 nanosecond

TEST: 8, RUN: 2, Iterations: 2187
  Concatenation =    526865 nanosecond
  Format        =   6294307 nanosecond
  StringBuilder =    591773 nanosecond

____________________________

TEST: 10, RUN: 1, Iterations: 19683
  Concatenation =   4592961 nanosecond
  Format        =  60114307 nanosecond
  StringBuilder =   2129387 nanosecond

TEST: 10, RUN: 2, Iterations: 19683
  Concatenation =   1850166 nanosecond
  Format        =  35940524 nanosecond
  StringBuilder =   1885544 nanosecond

  ____________________________

TEST: 12, RUN: 1, Iterations: 177147
  Concatenation =  26847286 nanosecond
  Format        = 126332877 nanosecond
  StringBuilder =  17578914 nanosecond

TEST: 12, RUN: 2, Iterations: 177147
  Concatenation =  24405056 nanosecond
  Format        = 129707207 nanosecond
  StringBuilder =  12253840 nanosecond
Дерек Зимба
источник
1
StringBuilder - абсолютно быстрый метод, когда вы добавляете символы в цикл, например, когда вы хотите создать строку с тысячей единиц, добавляя их одну за другой. Вот больше информации: pellegrino.link/2015/08/22/…
Карлос Ойос
Мне нравится, как вы всегда используете String.format для вывода: D, так что есть преимущество. и, честно говоря, если мы не говорим о миллионах итераций, я предпочитаю string.format для удобства чтения, поскольку ваш код демонстрирует очевидное преимущество!
Мохамнаг
9

Вот тот же тест, что и выше, с модификацией вызова метода toString () в StringBuilder . Результаты ниже показывают, что подход StringBuilder немного медленнее, чем конкатенация строк с использованием оператора + .

файл: StringTest.java

class StringTest {

  public static void main(String[] args) {

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("Hi to you ").append(i * 2).toString();
    }

    end = System.currentTimeMillis();

    System.out.println("String Builder = " + ((end - start)) + " millisecond");

  }
}

Команды оболочки: (скомпилируйте и запустите StringTest 5 раз)

> javac StringTest.java
> sh -c "for i in \$(seq 1 5); do echo \"Run \${i}\"; java StringTest; done"

Полученные результаты :

Run 1
Format = 1290 millisecond
Concatenation = 115 millisecond
String Builder = 130 millisecond

Run 2
Format = 1265 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

Run 3
Format = 1303 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 4
Format = 1297 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 5
Format = 1270 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond
Akos Cz
источник
6

String.format()это больше, чем просто объединение строк. Например, вы можете отображать числа в определенной локали, используя String.format().

Однако, если вас не волнует локализация, функциональной разницы нет. Может быть, один быстрее, чем другой, но в большинстве случаев он будет незначительным ..

Fortega
источник
4

Как правило, объединение строк должно быть предпочтительнее, чем String.format. Последний имеет два основных недостатка:

  1. Он не кодирует строку, которая будет построена локально.
  2. Процесс сборки кодируется в виде строки.

Под пунктом 1 я имею в виду, что невозможно понять, что String.format()делает вызов за один последовательный проход. Один вынужден переходить назад и вперед между строкой формата и аргументами при подсчете позиции аргументов. Для коротких конкатенаций это не большая проблема. Однако в этих случаях конкатенация строк менее многословна.

Под пунктом 2 я подразумеваю, что важная часть процесса сборки кодируется в строке формата (с использованием DSL). Использование строк для представления кода имеет много недостатков. Он не является типично безопасным по типу и усложняет подсветку синтаксиса, анализ кода, оптимизацию и т. Д.

Конечно, при использовании инструментов или сред, внешних по отношению к языку Java, в игру могут вступать новые факторы.

Стефан Берсье
источник
2

Я не делал каких-либо конкретных тестов, но я думаю, что объединение может быть быстрее. String.format () создает новый Formatter, который, в свою очередь, создает новый StringBuilder (размером всего 16 символов). Это значительный объем накладных расходов, особенно если вы форматируете более длинную строку, а StringBuilder продолжает изменять размер.

Однако конкатенация менее полезна и труднее для чтения. Как всегда, стоит сделать тест на вашем коде, чтобы увидеть, что лучше. Различия могут быть незначительными в серверном приложении после загрузки пакетов ресурсов, локалей и т. Д. В память и кода JITted.

Возможно, в качестве лучшей практики было бы неплохо создать собственный форматтер с правильно подобранным StringBuilder (Appendable) и Locale и использовать его, если вам нужно много форматирования.

AngerClown
источник
2

Там может быть ощутимая разница.

String.format довольно сложный и использует регулярное выражение внизу, так что не делайте это привычкой использовать его везде, но только там, где вам это нужно.

StringBuilder будет на порядок быстрее (как кто-то здесь уже указал).

Павел Зиеминский
источник
1

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

Я использовал ту же программу, которую использовал Icaro в своем ответе выше, и добавил в нее дополнительный код для MessageFormatобъяснения показателей производительности.

  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = "Hi " + i + "; Hi to you " + i * 2;
    }
    long end = System.currentTimeMillis();
    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = String.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = MessageFormat.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("MessageFormat = " + ((end - start)) + " millisecond");
  }

Конкатенация = 69 миллисекунд

Формат = 1435 миллисекунд

MessageFormat = 200 миллисекунд

ОБНОВЛЕНИЕ:

Согласно отчету SonarLint, строки формата в стиле Printf должны использоваться правильно (squid: S3457)

Поскольку printf-styleстроки формата интерпретируются во время выполнения, а не проверяются компилятором, они могут содержать ошибки, которые приводят к созданию неправильных строк. Это правило статический проверяет корреляцию printf-styleстрок формата для их аргументов при вызове методов формата (...) из java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, и java.io.PrintWriterклассов и printf(...)метода java.io.PrintStreamили java.io.PrintWriterклассы.

Я заменил стиль printf на фигурные скобки и получил кое-что интересное, как показано ниже.

Объединение = 69 миллисекунд
Формат = 1107 миллисекунд
Формат: фигурные скобки = 416 миллисекунд
MessageFormat = 215 миллисекунд
MessageFormat: фигурные скобки = 2517 миллисекунд

Мой вывод:
как я уже отмечал выше, использование String.format с фигурными скобками должно быть хорошим выбором для получения преимуществ хорошей читаемости, а также производительности.

Ганеса Виджаякумар
источник
0

Вы не можете сравнивать String Concatenation и String.Format с помощью указанной выше программы.

Вы можете попробовать это также поменять местами использование ваших String.Format и Concatenation в вашем блоке кода, как показано ниже

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = String.format( "Hi %s; Hi to you %s",i, + i*2);
  }

  long end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
  start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }

  end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;
}

Вы будете удивлены, увидев, что Формат работает здесь быстрее. Это связано с тем, что созданные объекты могут быть не выпущены, и может возникнуть проблема с выделением памяти и, следовательно, с производительностью.

DotNetUser
источник
3
ты пробовал свой код? Конкатенация всегда в десять раз быстрее
Icaro
как насчет миллис, взятых для выполнения этого "System.currentTimeMillis ()": P.
Рехан
0

Требуется немного времени, чтобы привыкнуть к String.Format, но в большинстве случаев оно того стоит. В мире NRA (никогда ничего не повторяйте) чрезвычайно полезно хранить ваши токенизированные сообщения (ведение журнала или пользователя) в библиотеке констант (я предпочитаю то, что равно статическому классу) и вызывать их по мере необходимости с помощью String.Format независимо от того, используете ли вы локализуются или нет. Попытки использовать такую ​​библиотеку с методом конкатенации труднее читать, устранять неполадки, корректировать и управлять любым подходом, требующим конкатенации. Замена - это вариант, но я сомневаюсь, что он эффективен. После многих лет использования моя самая большая проблема с String.Format заключается в том, что длина вызова неудобно велика, когда я передаю его в другую функцию (например, Msg), но это легко обойти с помощью пользовательской функции, служащей псевдонимом. ,

user665056
источник