Чи краще використовувати String.format через рядок Concatenation на Java?


273

Чи є помітна різниця між використанням String.formatі String конкатенацією на 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)

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.


15
Всі вони є поганою практикою. Використовуйте StringBuilder.
Амір Рамінфар

8
StringBuilder тут недоступний (питання ОП стосувалося порівняння String.format за рядком Concatenation), але чи є у вас дані про виконання String Builder?
Ікаро

108
@AmirRaminar: Компілятор автоматично перетворює "+" на дзвінки до StringBuilder.
Мартін Шредер

40
@ MartinSchröder: Якщо ви запустите, javap -c StringTest.classви побачите, що компілятор перетворює "+" в StringBuilder автоматично, лише якщо ви не знаходитесь у циклі. Якщо конкатенація виконується в одному рядку, це те саме, що використовувати "+", але якщо ви використовуєте myString += "morechars";або myString += anotherString;в декількох рядках, ви помітите, що може бути створено більше одного StringBuilder, тому використання "+" не завжди настільки ефективно як StringBuilder.
ccpizza

7
@Joffrey: я мав на увазі те, що для циклів +не перетворюються, 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,190 INFO [TestMain] - конкатенація = 134 мілісекунди
  • 2012-01-11 16: 30: 46,313 INFO [TestMain] - String Builder = 117 мілісекунд

21
Тест StringBuilder не викликає toString (), тому це не є справедливим порівнянням. Я підозрюю, що ви виявите, що це помилка вимірювання ефективності конкатенації, якщо виправите цю помилку.
Jamey Sharp

15
У конкатенації і формат тестів, ви просили String. Для тесту StringBuilder, щоб бути справедливим, потрібен останній крок, який перетворює вміст StringBuilder в String. Ви робите це, зателефонувавши 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 не видалить цей код в першу чергу.
alobodzk

37

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

На противагу цьому, аргументи, які +можна перевірити компілятором.

Історія безпеки в(на якому formatмоделюється функція) довгий і лякає.


16
тільки для запису, сучасні IDE (наприклад, IntelliJ) допомагають у підрахунку аргументів та відповідності типів
Рон Кляйн

2
Хороший момент щодо компіляції, я рекомендую зробити ці перевірки через FindBugs (який може працювати в IDE або через Maven під час збирання), зауважте, що це також перевірятиме форматування у всіх ваших журналах! Це працює незалежно від користувачів IDE
Christophe Roussy

20

Який з них читабельніше залежить від того, як працює ваша голова.

Ви отримали свою відповідь прямо там.

Це питання особистого смаку.

Зв'язок струн незначно швидший, я думаю, але це має бути незначним.


3
Я згоден. Думаючи про відмінності в продуктивності, головним чином, це лише передчасна оптимізація - в тому випадку, коли профілювання може показати, що тут є проблема, то хвилюйтеся з цього приводу.
Jonik

3
Це справді питання особистого смаку, якщо проект невеликий і ніколи не має наміру бути інтернаціоналізованим у будь-якому змістовному сенсі. Інакше String.format всіляко виграє конкатенацію.
workmad3

4
Я не погоджуюсь. Незалежно від того, наскільки великий проект, ви навряд чи будете локалізувати кожен рядок, який коли-небудь побудований в ньому. Іншими словами, це залежить від ситуації (для чого використовуються рядки).
Jonik

Я не уявляю, як хто-небудь вважатиме "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 - це абсолютно найшвидший метод, коли ви додаєте символи в циклі, наприклад, коли ви хочете створити рядок з тисячею 1, додаючи їх по черзі. Ось додаткова інформація: pellegrino.link/2015/08/22/…
Carlos Hoyos

Мені подобається, як ти завжди використовуєш String.format для виводу: D, тому є перевага. і якщо чесно, якщо ми не говоримо про мільйони повторень, я віддаю перевагу string.format для читабельності, оскільки ваш код показує явну перевагу!
mohamnag

9

Ось такий же тест, що і вище, з модифікацією виклику методу toString () на StringBuilder . Наведені нижче результати показують, що підхід StringBuilder лише трохи повільніше, ніж об'єднання String за допомогою оператора + .

файл: 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

6

String.format()це більше, ніж просто об'єднання рядків. Наприклад, ви можете відображати номери в певній місцевості за допомогою String.format().

Однак якщо ви не переймаєтесь локалізацією, функціональної різниці немає. Можливо, одна швидша за іншу, але в більшості випадків вона буде незначною.


4

Як правило, слід віддавати перевагу строковому конкатенації String.format. Останній має два основні недоліки:

  1. Він не кодує рядок, який потрібно побудувати локально.
  2. Процес побудови закодований у рядок.

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

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

Звичайно, при використанні інструментів чи рамок, зовнішніх для мови Java, можуть грати нові фактори.


2

Я не робив жодних конкретних орієнтирів, але думаю, що конкатенація може бути швидшою. String.format () створює новий Форматтер, який, в свою чергу, створює новий StringBuilder (розміром всього 16 символів). Це велика кількість накладних витрат, особливо якщо ви форматуєте довший рядок і StringBuilder продовжує потребувати зміни розміру.

Однак конкатенація менш корисна і важче читати. Як завжди, варто зробити орієнтир у своєму коді, щоб побачити, що краще. Різниці можуть бути незначними в серверній програмі після того, як ваші пакети ресурсів, локалі тощо будуть завантажені в пам'ять і код JITted.

Можливо, як найкраща практика, було б хорошою ідеєю створити власний Форматтер із належним розміром StringBuilder (додається) та Locale та використовувати це, якщо у вас є багато форматування.


2

Можлива різниця.

String.format є досить складним і використовує регулярний вираз під ним, тому не зважайте використовувати його скрізь, а лише там, де вам це потрібно.

StringBuilder було б на порядок швидше (як хтось тут уже вказував).


1

Я думаю, що ми можемо працювати так, MessageFormat.formatяк це має бути добре як у читанні, так і в аспектах продуктивності.

Я використовував ту саму програму, яку використовував Ікаро у своїй вищевказаній відповіді, і я доповнив її додаванням коду для використання 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 мілісекунд

Формат повідомлення = 200 мілісекунд

ОНОВЛЕННЯ:

Відповідно до звіту SonarLint, рядки формату Printf повинні використовуватися правильно (кальмар: 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 мілісекунд
Повідомлення Формат = 215 мілісекунд Формат повідомлення: фігурні
дужки = 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") ;
}

Ви здивуєтеся, побачивши, що формат працює тут швидше. Це відбувається тому, що створені об'єкти можуть бути не випущені, і може виникнути проблема з розподілом пам'яті, а отже, і продуктивністю.


3
ви спробували свій код? Зв'язування завжди вдесятеро швидше
Ікаро

як щодо мільйонів, виконаних для виконання цього "System.currentTimeMillis ()": P.
rehan

0

Для звикання до String.Format потрібно небагато часу, але це варто в більшості випадків. У світі NRA (ніколи нічого не повторювати) надзвичайно корисно зберігати токенізовані повідомлення (журнал чи користувач) у постійній бібліотеці (я вважаю за краще, що є статичним класом) та зателефонувати їм за потребою за допомогою String.Format незалежно від того локалізуються чи ні. Спроба використовувати таку бібліотеку методом конкатенації важче читати, вирішувати проблеми, коректувати та керувати будь-яким підходом, який потребує конкатенації. Заміна - це варіант, але я сумніваюся, що вона є ефективною. Через роки використання моя найбільша проблема зі String.Format - це тривалість дзвінка незручно довга, коли я переношу його в іншу функцію (наприклад, Msg), але це легко обійти за допомогою спеціальної функції, яка служить псевдонімом .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.