Java의 String Concatenation보다 String.format을 사용하는 것이 더 나은 방법입니까?


273

String.formatJava에서 사용 및 문자열 연결 사이에 인식 가능한 차이가 있습니까?

나는 사용하는 경향이 String.format있지만 때로는 미끄러 져 연결을 사용합니다. 하나가 다른 것보다 낫는지 궁금합니다.

내가 보는 방식 String.format은 문자열을 "포맷"하는 데 더 많은 힘을줍니다. 연결은 실수로 여분의 % s를 넣거나 누락하는 것에 대해 걱정할 필요가 없음을 의미합니다.

String.format 또한 짧습니다.

더 읽기 쉬운 것은 머리가 어떻게 작동하는지에 달려 있습니다.


MessageFormat.format을 사용할 수 있다고 생각합니다. 자세한 내용은 답변 stackoverflow.com/a/56377112/1491414 를 참조하십시오.
Ganesa Vijayakumar

답변:


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
더 나은 늦게 결코보다는 임의의 자바 버전 : docs.oracle.com/javase/1.5.0/docs/api/java/util/...
AKSEL

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를 사용하십시오.
Amir Raminfar

8
StringBuilder의 범위를 벗어났습니다 (OP 질문은 String Concatenation과 String.format을 비교하는 것에 관한 것이지만) String Builder에 대한 데이터를 수행 했습니까?
Icaro

108
@AmirRaminar : 컴파일러는 "+"를 StringBuilder 호출로 자동 변환합니다.
Martin Schröder

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 정보 [TestMain]-형식 = 1416 밀리 초
  • 2012-01-11 16 : 30 : 46,190 정보 [TestMain]-연결 = 134 밀리 초
  • 2012-01-11 16 : 30 : 46,313 정보 [TestMain]-문자열 작성기 = 117 밀리 초

21
StringBuilder 테스트는 toString ()을 호출하지 않으므로 공정한 비교가 아닙니다. 해당 버그를 수정하면 연결 성능의 측정 오류 내에 있음을 알 수 있습니다.
Jamey Sharp

15
연결 및 형식 테스트에서을 요청했습니다 String. 공정한 StringBuilder 테스트는 StringBuilder의 내용을 String으로 바꾸는 마지막 단계가 필요합니다. 당신은 전화로 그렇게합니다 bldString.toString(). 나는 그것이 그것을 설명하기를 바랍니다?
Jamey Sharp

4
Jamey Sharp가 옳습니다. bldString.toString () 호출은 문자열 연결보다 느리지 않은 경우와 거의 같습니다.
Akos Cz

3
String s = bldString.toString(); 타이밍은 거의 서로 파에 연결하고 모두 StringBuilder와 함께했다 : Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond 내가 루프를 실행하고 좋은 담당자를 얻기 위해 각 평균화 : (내가 시간을 얻을 때, 10000 루프를 시도 할 것이다 사전 JVM을 최적화)
TechTrip

3
코드가 실행되는지 어떻게 알 수 있습니까? 변수를 읽거나 사용하지 않으므로 JIT가이 코드를 먼저 제거하지 않을 수 없습니다.
alobodzk

37

한 가지 문제 .format는 정적 유형 안전을 잃는다는 것입니다. 형식에 대한 인수가 너무 적을 수 있으며 형식 지정자에 대해 잘못된 유형을 가질 수 있습니다. 둘 다 IllegalFormatException 런타임 에 발생하므로 프로덕션을 중단시키는 로깅 코드가 생길 수 있습니다.

반대로, 인수 +는 컴파일러에서 테스트 할 수 있습니다.

보안 역사( format함수가 모델링 된) 길고 무섭습니다.


16
현대 IDE (예 : IntelliJ)는 인수 개수 및 유형 일치를 지원합니다.
Ron Klein

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 개씩 1000 개씩 추가하여 문자열을 만들려는 경우에 가장 빠른 방법입니다. 여기에 대한 추가 정보는 다음과 같습니다 pellegrino.link/2015/08/22/...
카를로스 호 요스

출력 : D에 항상 String.format을 사용하는 방식이 마음에 듭니다. 그래서 이점이 있습니다. 그리고 우리가 수백만 개의 이터 레이션에 대해 이야기하지 않는다면 솔직히 말하면 코드에서 명백한 이점을 보여주기 때문에 가독성을 위해 string.format을 선호합니다!
mohamnag

9

다음 은 StringBuilder 에서 toString () 메서드를 호출하여 수정 한 위와 동일한 테스트 입니다. 아래 결과는 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 ()은 새로운 포매터를 생성하고, 새로운 포매터는 새로운 16 글자 크기의 새로운 StringBuilder를 생성합니다. 더 긴 문자열을 포맷하고 StringBuilder가 크기를 조정 해야하는 경우 특히 오버 헤드가 많이 발생합니다.

그러나 연결은 덜 유용하고 읽기가 어렵습니다. 항상 그렇듯이 코드에서 벤치 마크를 수행하여 어느 것이 더 나은지 알아볼 가치가 있습니다. 리소스 번들, 로케일 등이 메모리에로드되고 코드가 JIT 된 후 서버 앱에서 차이점을 무시할 수 있습니다.

가장 좋은 방법은 적절한 크기의 StringBuilder (Appendable) 및 Locale을 사용하여 고유 한 Formatter를 만들고 서식이 많은 경우이 형식을 사용하는 것이 좋습니다.


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 스타일 형식 문자열을 올바르게 사용해야합니다 (오징어 : 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") ;
}

여기서 형식이 더 빨리 작동한다는 사실에 놀랄 것입니다. 작성된 초기 오브젝트가 해제되지 않아 메모리 할당 및 성능에 문제가있을 수 있기 때문입니다.


3
코드를 사용해 보셨습니까? 연결은 항상 10 배 빠릅니다
Icaro

이 "System.currentTimeMillis ()": P를 실행하기 위해 밀은 어떻습니까?
rehan

0

String.Format에 익숙해지는 데 약간의 시간이 걸리지 만 대부분의 경우 가치가 있습니다. NRA의 세계에서 (아무것도 반복하지 마십시오) 토큰 화 된 메시지 (로깅 또는 사용자)를 상수 라이브러리 (정적 클래스에 어느 정도 선호하는지)에 보관하고 필요에 따라 String을 사용하여 호출하는 것이 매우 유용합니다. 현지화 여부 연결 방법으로 이러한 라이브러리를 사용하려고하면 연결이 필요한 모든 방법으로 읽기, 문제 해결, 교정 및 관리하기가 더 어렵습니다. 교체는 옵션이지만 성능이 의심 스럽다. 몇 년 동안 사용한 후 String.Format의 가장 큰 문제는 호출과 같은 다른 함수 (Msg와 같은)에 전달할 때 호출 길이가 불편하지만 별칭으로 사용하기 위해 사용자 정의 함수로 쉽게 해결할 수 있다는 것입니다. .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.