[java] 문자열 연결 : concat () 및“+”연산자

문자열 a와 b를 가정 :

a += b
a = a.concat(b)

후드 아래에서 같은 것입니까?

다음은 참조로 디 컴파일 된 concat입니다. +연산자 를 디 컴파일하고 그 기능을 확인하고 싶습니다 .

public String concat(String s) {

    int i = s.length();
    if (i == 0) {
        return this;
    }
    else {
        char ac[] = new char[count + i];
        getChars(0, count, ac, 0);
        s.getChars(0, i, ac, count);
        return new String(0, count + i, ac);
    }
}



답변

아뇨.

첫째, 시맨틱에는 약간의 차이가 있습니다. 경우 a이며 null, 다음 a.concat(b)을 던졌습니다 NullPointerException하지만 a+=b의 원래 값으로 취급 a이 마치를 null. 또한이 concat()메서드는 String값만 받아들이는 반면 +연산자는 인수를 String으로 자동 변환합니다 ( toString()객체 의 메서드 사용 ). 따라서이 concat()방법은 허용 되는 방식이 더 엄격합니다.

후드를 살펴 보려면 간단한 클래스를 작성하십시오. a += b;

public class Concat {
    String cat(String a, String b) {
        a += b;
        return a;
    }
}

이제 분해합니다 javap -c(Sun JDK에 포함). 다음을 포함한 목록이 나타납니다.

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

그래서, a += b하는 것과 동일

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();

concat방법은 빨리해야한다. 그러나 더 많은 문자열을 사용하면 StringBuilder적어도 성능 측면 에서이 방법이 승리합니다.

소스 코드 StringStringBuilder패키지 전용 기본 클래스는 Sun JDK의 src.zip에 있습니다. char 배열을 만들고 (필요한 경우 크기 조정) final을 만들 때 버리는 것을 알 수 있습니다 String. 실제로 메모리 할당은 놀라 울 정도로 빠릅니다.

업데이트 : Pawel Adamski가 언급 한 것처럼 최신 HotSpot에서 성능이 변경되었습니다. javac여전히 정확히 동일한 코드를 생성하지만 바이트 코드 컴파일러는 속임수를 사용합니다. 코드 전체가 버려지기 때문에 간단한 테스트가 완전히 실패합니다. 합산 System.identityHashCode(not String.hashCode)은 StringBuffer코드가 약간의 이점을 가지고 있음을 보여줍니다 . 다음 업데이트가 릴리스되거나 다른 JVM을 사용하는 경우 변경 될 수 있습니다. 에서 @lukaseder , 핫스팟 JVM의 내장 함수의 목록 .


답변

Niyaz 는 정확하지만 특수 + 연산자를 Java 컴파일러에서 더 효율적인 것으로 변환 할 수 있다는 점도 주목할 가치가 있습니다. Java에는 스레드로부터 안전하고 변경 가능한 문자열을 나타내는 StringBuilder 클래스가 있습니다. 많은 문자열 연결을 수행하면 Java 컴파일러는 자동으로 변환합니다.

String a = b + c + d;

으로

String a = new StringBuilder(b).append(c).append(d).toString();

큰 문자열의 경우 훨씬 효율적입니다. 내가 아는 한 concat 메소드를 사용할 때 발생하지 않습니다.

그러나 빈 문자열을 기존 문자열에 연결할 때 concat 메소드가 더 효율적입니다. 이 경우 JVM은 새 String 객체를 만들 필요가 없으며 기존 객체를 반환 할 수 있습니다. 이를 확인 하려면 concat 설명서 를 참조하십시오 .

따라서 효율성에 대해 잘 알고 있다면 비어있는 문자열을 연결할 때 concat 메소드를 사용하고 그렇지 않으면 +를 사용해야합니다. 그러나 성능 차이는 무시해도 좋을 것이므로 걱정하지 않아도됩니다.


답변

@marcio와 비슷한 테스트를 실행했지만 대신 다음 루프를 사용했습니다.

String c = a;
for (long i = 0; i < 100000L; i++) {
    c = c.concat(b); // make sure javac cannot skip the loop
    // using c += b for the alternative
}

좋은 측정을 위해서, 나는 또한 들어갔다 StringBuilder.append(). 각 테스트는 10 회 실행되었으며, 각 실행마다 100k 반복했습니다. 결과는 다음과 같습니다.

  • StringBuilder손을 이깁니다. 대부분의 런에서 클록 시간 결과는 0이었고 가장 긴 시간은 16ms였습니다.
  • a += b 각 실행에 약 40000ms (40s)가 소요됩니다.
  • concat 실행 당 10000ms (10s) 만 필요합니다.

내부를 보거나 아직 프로파일 러를 통해 클래스를 디 컴파일하지는 않았지만 a += b새 객체를 StringBuilder만든 다음 다시로 변환하는 데 많은 시간을 소비 한다고 생각합니다 String.


답변

여기에있는 대부분의 대답은 2008 년에 대한 것입니다. 시간이지나면서 상황이 변한 것 같습니다. JMH로 만든 최신 벤치 마크는 Java 8에서 +보다 약 2 배 빠릅니다 concat.

내 벤치 마크 :

@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State2 {
        public String a = "abc";
        public String b = "xyz";
    }

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State3 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
    }


    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State4 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
        public String d = "!@#";
    }

    @Benchmark
    public void plus_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b);
    }

    @Benchmark
    public void plus_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c);
    }

    @Benchmark
    public void plus_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c+state.d);
    }

    @Benchmark
    public void stringbuilder_2(State2 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
    }

    @Benchmark
    public void stringbuilder_3(State3 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
    }

    @Benchmark
    public void stringbuilder_4(State4 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
    }

    @Benchmark
    public void concat_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b));
    }

    @Benchmark
    public void concat_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c)));
    }


    @Benchmark
    public void concat_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
    }
}

결과 :

Benchmark                             Mode  Cnt         Score         Error  Units
StringConcatenation.concat_2         thrpt   50  24908871.258 ± 1011269.986  ops/s
StringConcatenation.concat_3         thrpt   50  14228193.918 ±  466892.616  ops/s
StringConcatenation.concat_4         thrpt   50   9845069.776 ±  350532.591  ops/s
StringConcatenation.plus_2           thrpt   50  38999662.292 ± 8107397.316  ops/s
StringConcatenation.plus_3           thrpt   50  34985722.222 ± 5442660.250  ops/s
StringConcatenation.plus_4           thrpt   50  31910376.337 ± 2861001.162  ops/s
StringConcatenation.stringbuilder_2  thrpt   50  40472888.230 ± 9011210.632  ops/s
StringConcatenation.stringbuilder_3  thrpt   50  33902151.616 ± 5449026.680  ops/s
StringConcatenation.stringbuilder_4  thrpt   50  29220479.267 ± 3435315.681  ops/s


답변

Tom은 + 연산자의 기능을 정확하게 설명합니다. 임시를 생성 StringBuilder하고 파트를 추가 한 다음로 마무리합니다 toString().

그러나 지금까지의 모든 답변은 HotSpot 런타임 최적화의 영향을 무시하고 있습니다. 특히 이러한 임시 작업은 일반적인 패턴으로 인식되어 런타임시보다 효율적인 기계 코드로 대체됩니다.

@ marcio : 당신은 마이크로 벤치 마크를 만들었습니다 ; 최신 JVM에서는 코드를 프로파일 링하는 올바른 방법이 아닙니다.

런타임 최적화가 중요한 이유는 객체 생성을 포함하여 코드의 이러한 차이가 HotSpot이 시작되면 완전히 달라지기 때문입니다. 확실하게 알 수있는 유일한 방법은 코드 를 현장에서 프로파일 링하는 것 입니다.

마지막으로 이러한 모든 방법은 실제로 매우 빠릅니다. 조기 최적화의 경우 일 수 있습니다. 문자열을 많이 연결하는 코드가있는 경우 최대 속도를 얻는 방법은 선택한 연산자와 관련이 없으며 대신 사용중인 알고리즘과 관련이 없습니다!


답변

간단한 테스트는 어떻습니까? 아래 코드를 사용했습니다.

long start = System.currentTimeMillis();

String a = "a";

String b = "b";

for (int i = 0; i < 10000000; i++) { //ten million times
     String c = a.concat(b);
}

long end = System.currentTimeMillis();

System.out.println(end - start);
  • "a + b"버전에서 실행 2500ms .
  • a.concat(b)에서 실행 1200ms .

여러 번 테스트했습니다. concat()버전의 실행은 평균적으로 시간의 절반을했다.

이 결과는 concat()메서드가 항상 새로운 문자열을 생성 하기 때문에 놀랐습니다 ( ” new String(result)“를 반환합니다 . 잘 알려져 있습니다 :

String a = new String("a") // more than 20 times slower than String a = "a"

컴파일러가 “a + b”코드에서 문자열 생성을 최적화 할 수 없었던 이유는 무엇입니까? 새로운 문자열 생성을 피할 수 있습니다. 위의 진술을 믿지 않으면 스스로 테스트하십시오.


답변

기본적으로 +와 concat방법 사이에는 두 가지 중요한 차이점이 있습니다.

  1. concat 메소드를 사용하는 경우 + 연산자의 경우 문자열 만 연결할 수 있으며 문자열을 모든 데이터 유형으로 연결할 수도 있습니다.

    예를 들어 :

    String s = 10 + "Hello";

    이 경우 출력은 10Hello 이어야합니다 .

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);

    위의 경우 두 개의 문자열을 필수로 제공해야합니다.

  2. +concat 의 두 번째 주요 차이점은 다음 과 같습니다.

    사례 1 : 이 방법으로 concat 연산자를
    사용하여 동일한 문자열을 연결 한다고 가정하십시오 .

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);

    이 경우 풀에서 생성 된 총 개체 수는 다음과 같습니다.

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy

    사례 2 :

    이제 + 연산자 를 통해 동일한 문자열을 연결하려고합니다.

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);

    위의 경우 생성 된 총 개체 수는 5입니다.

    실제로 + 연산자를 통해 문자열을 연결 하면 StringBuffer 클래스를 유지하여 다음과 같은 작업을 수행합니다.

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);

    이런 식으로 5 개의 객체 만 생성합니다.

사람들은 이것이 +concat 방법 의 기본적인 차이점 입니다. 즐겨 🙂