Czy lepszą praktyką jest używanie String.format zamiast String Concatenation w Javie?

273

Czy jest zauważalna różnica między używaniem String.formata łączeniem ciągów w Javie?

Zwykle używam, String.formatale czasami poślizgnę się i użyję konkatenacji. Zastanawiałem się, czy jedno jest lepsze od drugiego.

Sposób, w jaki go widzę, String.formatdaje więcej mocy w „formatowaniu” łańcucha; a konkatenacja oznacza, że ​​nie musisz się martwić o przypadkowe dodanie dodatkowych% s lub pominięcie jednego.

String.format jest również krótszy.

Który z nich jest bardziej czytelny, zależy od tego, jak działa Twoja głowa.

Omar Kooheji
źródło
Myślę, że możemy przejść z MessageFormat.format. Więcej informacji można znaleźć w odpowiedzi stackoverflow.com/a/56377112/1491414 .
Ganesa Vijayakumar

Odpowiedzi:

242

Sugerowałbym, że jest to lepsza praktyka w użyciu String.format(). Głównym powodem jest to, że String.format()można go łatwiej zlokalizować dzięki tekstowi ładowanemu z plików zasobów, podczas gdy konkatenacji nie można zlokalizować bez wytworzenia nowego pliku wykonywalnego z innym kodem dla każdego języka.

Jeśli planujesz lokalizację swojej aplikacji, powinieneś również przyzwyczaić się do określania pozycji argumentów dla swoich tokenów formatu:

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

Można to następnie zlokalizować i zamienić tokeny nazwy i czasu bez konieczności ponownej kompilacji pliku wykonywalnego w celu uwzględnienia różnych kolejności. Dzięki pozycjom argumentów możesz również ponownie użyć tego samego argumentu, nie przekazując go dwukrotnie do funkcji:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)
workmad3
źródło
1
Czy możesz wskazać mi dokumentację, która mówi o tym, jak pracować z pozycjami / kolejnością argumentów w Javie (tj. Jak odwoływać się do argumentów według ich pozycji)? Dzięki.
markvgti
13
Lepsza późno niż wcale, losowa wersja Java: docs.oracle.com/javase/1.5.0/docs/api/java/util/…
Aksel
174

O wydajności:

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");
}

Wyniki pomiaru czasu są następujące:

  • Łączenie = 265 milisekund
  • Format = 4141 milisekund

Dlatego konkatenacja jest znacznie szybsza niż String.format.

Icaro
źródło
15
Wszystkie są złą praktyką. Użyj StringBuilder.
Amir Raminfar,
8
StringBuilder jest tutaj poza zakresem (pytanie OP dotyczyło porównania String.format z ciągiem Concatenation), ale czy wykonałeś dane dotyczące String Builder?
Icaro
108
@AmirRaminar: Kompilator automatycznie konwertuje „+” na wywołania StringBuilder.
Martin Schröder
40
@ MartinSchröder: Jeśli uruchomisz javap -c StringTest.class, zobaczysz, że kompilator konwertuje „+” na StringBuilder automatycznie tylko wtedy, gdy nie jesteś w pętli. Jeśli łączenie odbywa się w jednym wierszu, jest to to samo, co użycie „+”, ale jeśli używasz myString += "morechars";lub myString += anotherString;w wielu wierszach, zauważysz, że można utworzyć więcej niż jeden StringBuilder, więc użycie „+” nie zawsze jest tak wydajne jako StringBuilder.
ccpizza
7
@Joffrey: miałem na myśli to, że pętle +nie są konwertowane na, StringBuilder.append()ale zamiast tego new StringBuilder()dzieje się przy każdej iteracji.
ccpizza
39

Ponieważ jest dyskusja na temat wydajności, pomyślałem, że dodam do porównania, które obejmowało StringBuilder. Jest to w rzeczywistości szybsze niż concat i oczywiście opcja String.format.

Aby uczynić to swego rodzaju porównanie jabłek z jabłkami, tworzę instancję nowego StringBuilder w pętli, a nie na zewnątrz (jest to w rzeczywistości szybsze niż wykonanie tylko jednej instancji, najprawdopodobniej z powodu narzutu związanego z ponownym przydzielaniem miejsca dla pętli dołączanej na końcu jeden konstruktor).

    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] - Format = 1416 milisekund
  • 2012-01-11 16: 30: 46,190 INFO [TestMain] - konkatenacja = 134 milisekundy
  • 2012-01-11 16: 30: 46,313 INFO [TestMain] - String Builder = 117 milisekund
TechTrip
źródło
21
Test StringBuilder nie wywołuje metody toString (), więc nie jest to uczciwe porównanie. Podejrzewam, że znajdziesz błąd w pomiarze wydajności konkatenacji, jeśli naprawisz ten błąd.
Jamey Sharp
15
W testach konkatenacji i formatu poprosiłeś o String. Test StringBuilder, aby być uczciwym, wymaga ostatniego kroku, który zamienia zawartość StringBuilder w String. Robisz to dzwoniąc bldString.toString(). Mam nadzieję, że to wyjaśnia?
Jamey Sharp
4
Jamey Sharp ma dokładnie rację. Wywołanie bldString.toString () jest mniej więcej takie samo, jeśli nie wolniejsze niż konkatenacja łańcuchów.
Akos Cz
3
String s = bldString.toString(); Te czasy były z konkatenacji i StringBuilder prawie na równi ze sobą: Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond wpadłem je w pętli i uśredniane każdy obecnie, aby uzyskać dobrą Czeska: (pre-JVM optymalizacji, postara się 10000+ pętlę kiedy mam czas)
TechTrip
3
Skąd w ogóle wiecie, czy kod jest w ogóle wykonywany? Zmienne nigdy nie są odczytywane ani używane, nie możesz być pewien, że JIT nie usunie tego kodu w pierwszej kolejności.
alobodzk
37

Jednym z problemów .formatjest to, że tracisz bezpieczeństwo typu statycznego. Możesz mieć za mało argumentów dla swojego formatu i możesz mieć niewłaściwe typy dla specyfikatorów formatu - oba prowadzą do działania IllegalFormatException w czasie wykonywania , więc możesz skończyć z kodem rejestrującym, który przerywa produkcję.

Natomiast argumenty do +przetestowania przez kompilator.

Historia zabezpieczeń z(na którym formatmodelowana jest funkcja) jest długi i przerażający.

Martin Schröder
źródło
16
tylko dla przypomnienia, nowoczesne IDE (np. IntelliJ) pomagają w liczeniu argumentów i dopasowywaniu typów
Ron Klein
2
Dobra uwaga na temat kompilacji, polecam wykonanie tych kontroli za pomocą FindBugs (które mogą być uruchamiane w IDE lub przez Maven podczas kompilacji), pamiętaj, że spowoduje to sprawdzenie formatowania podczas całego logowania! Działa to niezależnie od IDE użytkowników
Christophe Roussy
20

Który z nich jest bardziej czytelny, zależy od tego, jak działa Twoja głowa.

Masz swoją odpowiedź właśnie tam.

To kwestia osobistego gustu.

Łączenie łańcuchów jest, jak sądzę, nieznacznie szybsze, ale powinno to być nieistotne.

Thilo
źródło
3
Zgadzam się. Myślenie o różnicach wydajności tutaj jest przede wszystkim przedwczesną optymalizacją - w mało prawdopodobnym przypadku, gdy profilowanie pokazuje, że jest tu problem, to się martw.
Jonik
3
To naprawdę kwestia osobistego gustu, jeśli projekt jest mały i nigdy nie miał być umiędzynarodowiony w żadnym sensownym sensie. W przeciwnym razie String.format pod każdym względem wygrywa z konkatenacją.
workmad3
4
Nie zgadzam się. Bez względu na to, jak duży jest projekt, prawie nie zlokalizujesz każdego łańcucha, który kiedykolwiek w nim skonstruowano. Innymi słowy, zależy to od sytuacji (jakie są używane łańcuchy).
Jonik
Nie mogę sobie wyobrazić, jak ktokolwiek kiedykolwiek uznałby „String.format („% s% s ”, a, b)” za bardziej czytelny niż „a + b” i biorąc pod uwagę różnicę prędkości rzędu rzędów wielkości, odpowiedź wydaje mi się jasna (w sytuacjach, które nie będą wymagały lokalizacji, takich jak debugowanie lub większość instrukcji logowania).
BobDoolittle
16

Oto test z wieloma wielkościami próbek w milisekundach.

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
Derek Ziemba
źródło
1
StringBuilder jest absolutnie najszybszą metodą, gdy dodajesz znaki w pętli, na przykład, gdy chcesz utworzyć ciąg z tysiącem 1, dodając je jeden po drugim. Oto więcej informacji: pellegrino.link/2015/08/22/…
Carlos Hoyos,
Podoba mi się to, jak zawsze używasz String.format dla wyjścia: D, więc jest zaleta. i szczerze mówiąc, jeśli nie mówimy o milionach iteracji, wolę string.format ze względu na czytelność, ponieważ kod pokazuje oczywistą przewagę!
mohamnag
9

Oto taki sam test jak powyżej z modyfikacją wywołania metody toString () na StringBuilder . Poniższe wyniki pokazują, że podejście StringBuilder jest nieco wolniejsze niż łączenie String za pomocą operatora + .

plik: 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");

  }
}

Polecenia powłoki: (skompiluj i uruchom StringTest 5 razy)

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

Wyniki:

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
źródło
6

String.format()to coś więcej niż tylko łączenie łańcuchów. Na przykład możesz wyświetlać liczby w określonych ustawieniach narodowych za pomocą String.format().

Jeśli jednak nie zależy Ci na lokalizacji, nie ma żadnej różnicy funkcjonalnej. Być może jedno jest szybsze od drugiego, ale w większości przypadków będzie nieistotne.

Fortega
źródło
4

Ogólnie rzecz biorąc, należy preferować łączenie łańcuchów String.format. Ta ostatnia ma dwie główne wady:

  1. Nie koduje ciągu, który ma zostać zbudowany w sposób lokalny.
  2. Proces budowania jest zakodowany w ciągu.

Przez punkt 1 rozumiem, że nie jest możliwe zrozumienie, co String.format()robi połączenie w pojedynczym przebiegu sekwencyjnym. Zmusza się do przechodzenia między ciągiem formatu a argumentami, licząc jednocześnie pozycję argumentów. W przypadku krótkich konkatenacji nie stanowi to większego problemu. Jednak w tych przypadkach łączenie łańcuchów jest mniej szczegółowe.

Przez punkt 2 rozumiem, że ważna część procesu budowania jest zakodowana w ciągu formatu (przy użyciu DSL). Używanie ciągów do reprezentowania kodu ma wiele wad. Nie jest z natury bezpieczny dla typu i komplikuje podświetlanie składni, analizę kodu, optymalizację itp.

Oczywiście przy użyciu narzędzi lub struktur zewnętrznych w stosunku do języka Java mogą się pojawić nowe czynniki.

Stephane Bersier
źródło
2

Nie przeprowadziłem żadnych konkretnych testów porównawczych, ale sądzę, że konkatenacja może być szybsza. String.format () tworzy nowy moduł formatujący, który z kolei tworzy nowy StringBuilder (o rozmiarze tylko 16 znaków). To spora kwota narzutu, szczególnie jeśli formatujesz dłuższy ciąg, a StringBuilder wciąż musi zmieniać rozmiar.

Jednak łączenie jest mniej przydatne i trudniejsze do odczytania. Jak zawsze warto zrobić test porównawczy w kodzie, aby zobaczyć, co jest lepsze. Różnice mogą być nieistotne w aplikacji serwerowej po załadowaniu pakietów zasobów, ustawień regionalnych itp. Do pamięci i kodowaniu JIT.

Być może najlepszym rozwiązaniem byłoby utworzenie własnego narzędzia do formatowania z odpowiednio dobranym StringBuilder (Appendable) i ustawieniami regionalnymi i użycie tego, jeśli masz dużo formatowania.

AngerClown
źródło
2

Może być zauważalna różnica.

String.format jest dość złożony i używa wyrażenia regularnego pod spodem, więc nie rób z niego nawyku, aby używać go wszędzie, ale tylko tam, gdzie jest to potrzebne.

StringBuilder byłoby o rząd wielkości szybsze (jak ktoś tutaj już zauważył).

Paweł Ziemiński
źródło
1

Myślę, że możemy iść z tym, MessageFormat.formatponieważ powinna być dobra zarówno pod względem czytelności, jak i aspektów wydajnościowych.

Użyłem tego samego programu, którego użył Icaro w swojej powyższej odpowiedzi, i wzbogaciłem go o dołączony kod służący MessageFormatdo wyjaśnienia liczb wykonania.

  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");
  }

Łączenie = 69 milisekund

Format = 1435 milisekund

MessageFormat = 200 milisekund

AKTUALIZACJE:

Zgodnie z raportem SonarLint ciągi formatu formatu Printf powinny być używane poprawnie (squid: S3457)

Ponieważ printf-styleciągi formatu są interpretowane w czasie wykonywania, a nie sprawdzane przez kompilator, mogą zawierać błędy, które powodują tworzenie niewłaściwych ciągów. Zasada ta sprawdza się statycznie korelacji printf-styleciągi formatu na ich argumentów podczas wywoływania formatu (...) metod java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, i java.io.PrintWriterKlasy i printf(...)metody java.io.PrintStreamlub java.io.PrintWriterklasy.

Zamieniam styl printf na nawiasy klamrowe i uzyskałem coś interesującego, jak poniżej.

Łączenie = 69 milisekund
Format = 1107 milisekund
Format: nawiasy klamrowe = 416 milisekund
MessageFormat = 215 milisekund
MessageFormat: nawiasy klamrowe = 2517 milisekund

Mój wniosek:
Jak podkreśliłem powyżej, użycie String.format z nawiasami klamrowymi powinno być dobrym wyborem, aby uzyskać korzyści z dobrej czytelności, a także wydajności.

Ganesa Vijayakumar
źródło
0

Nie można porównać String Concatenation i String.Format przez powyższy program.

Możesz spróbować zmienić to również przy użyciu swojego String.Format i Concatenation w swoim bloku kodu, jak poniżej

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") ;
}

Będziesz zaskoczony, widząc, że Format działa tutaj szybciej. Jest tak, ponieważ utworzone obiekty początkowe mogą nie zostać zwolnione i może wystąpić problem z alokacją pamięci, a tym samym wydajnością.

DotNetUser
źródło
3
próbowałeś swojego kodu? Łączenie jest zawsze dziesięć razy szybsze
Icaro,
co z millis wykonanymi w celu wykonania tego „System.currentTimeMillis ()”: P.
rehan
0

Przyzwyczajenie się do String.Format zajmuje trochę czasu, ale w większości przypadków jest tego warte. W świecie NRA (nigdy niczego nie powtarzaj) niezwykle przydatne jest przechowywanie tokenizowanych wiadomości (rejestrowania lub użytkownika) w bibliotece Constant (wolę, co stanowi klasę statyczną) i wywoływanie ich w razie potrzeby za pomocą String.Format bez względu na to, czy lokalizują czy nie. Próba użycia takiej biblioteki z metodą konkatenacji jest trudniejsza do odczytania, rozwiązywania problemów, korekty i zarządzania przy użyciu dowolnego podejścia wymagającego konkatenacji. Zastąpienie jest opcją, ale wątpię, czy jest wydajne. Po latach użytkowania moim największym problemem z String.Format jest to, że długość połączenia jest niewygodnie długa, gdy przekazuję go do innej funkcji (np. Msg), ale łatwo jest obejść tę funkcję za pomocą aliasu .

user665056
źródło