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


499

문자열 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);
    }
}


3
+디 컴파일 할 수 있는지 잘 모르겠습니다 .
Galen Nare

1
javap 를 사용 하여 Java 클래스 파일을 디스 어셈블하십시오.
핫 릭

'불변성'에 당신은 아마 사용해야 인해 StringBuffer또는 StringBuilder- (대신 빠른 따라서 안전하지 않은 스레드
Ujjwal 싱

답변:


560

아뇨.

첫째, 시맨틱에는 약간의 차이가 있습니다. 경우 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의 내장 함수의 목록 .


4
@HyperLink 코드를 사용 javap -c하는 컴파일 된 클래스를 사용 하여 코드를 볼 수 있습니다 . (오, 답 에서처럼. 당신은 바이트 코드 디스 어셈블리를 해석하기 만하면됩니다. 그렇게 어렵지 않아야합니다.)
Tom Hawtin-tackline

1
JVM 스펙 을 참조 하여 개별 바이트 코드를 이해할 수 있습니다 . 참조하고자하는 내용은 6 장에 있습니다. 약간 애매하지만, 그 요점을 상당히 쉽게 얻을 수 있습니다.
핫 릭

1
StringBuilder두 개의 문자열을 결합 할 때 Java 컴파일러가 왜 사용하는지 궁금합니다 . String최대 4 개의 문자열 또는에있는 모든 문자열을 연결하는 정적 메서드가 포함 된 경우 String[]코드는 2 개의 객체 할당 (결과 String및 해당 백업 char[]은 하나도 중복되지 않음)과 3 개의 할당을 갖는 문자열은 최대 4 개까지 추가 할 수 있습니다 ( String[]결과 String및 배면 char[]만을 먼저 존재 리던던트 함께). 그대로 StringBuilder의지 사용 하려면 최대 4 개의 할당이 필요하며 모든 문자를 두 번 복사해야합니다.
supercat

그 표현은 a + = b입니다. a = a + b를 의미합니까?
가장 존경받는 선생님

3
이 답변이 작성된 이후로 상황이 변경되었습니다. 아래 답변을 읽으십시오.
Paweł Adamski

90

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 메소드를 사용하고 그렇지 않으면 +를 사용해야합니다. 그러나 성능 차이는 무시해도 좋을 것이므로 걱정하지 않아도됩니다.


concat 사실은 그렇게하지 않습니다. 나는 CONCAT 방법의 컴파일에 내 게시물을 편집 한
shsteimer

10
실제로 그렇습니다. 연결 코드의 첫 줄을보십시오. CONCAT의 문제는 항상 새로운 문자열을 생성하는 것입니다 ()
마르 Aguiar

2
@MarcioAguiar는 : 어쩌면 당신은 + 항상 새로운 생성을 의미 String- 당신이 말한대로 concat당신이 빈을 CONCAT 때 한 가지 예외가 있습니다 String.
Blaisorblade

45

@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.


4
객체 생성 시간이 정말로 중요합니다. 그렇기 때문에 많은 상황에서 StringBuilder를 + 뒤에 사용하지 않고 StringBuilder를 직접 사용합니다.
coolcfan

1
@coolcfan : +두 문자열에 사용될 때 사용하는 StringBuilder것보다 나은 경우가 String.valueOf(s1).concat(s2)있습니까? 컴파일러는 후자 [그렇지 않으면 생략을 사용하지 왜 어떤 생각 valueOf의 경우 전화 s1null이 아닌 것으로 알려져있다]를?
supercat

1
@supercat 죄송합니다. 모르겠습니다. 이 설탕 뒤에있는 사람들이 이것에 가장 적합한 사람들 일 것입니다.
coolcfan

25

여기에있는 대부분의 대답은 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

Java String의 요소를 연결하여 문자열을 형성하는 정적 함수를 포함하지 않은 이유가 궁금 합니다 String[]. 사용하여 +구성하고 나중에 포기 필요로 이러한 기능을 사용하여 8 개 문자열을 연결하는 String[8]A가 사용하는 동안,하지만 포기 구축 할 필요가있는 유일한 객체가 될 것 StringBuilder구성하고 포기 요구 StringBuilder인스턴스와 적어도 하나 명의 char[]백업 저장소를.
supercat

@supercat 클래스 String.join()주위의 빠른 구문 래퍼로 일부 정적 메서드가 Java 8에 추가되었습니다 java.util.StringJoiner.
Ti Strga

@TiStrga : 처리 +기능이 그러한 기능을 사용하도록 변경 되었습니까?
supercat

@supercat 바이너리 바이너리 호환성을 깨뜨릴 수 있습니다. 그것은 당신의 "문자열이 정적 기능을 포함하지 않을 이유"댓글에 답글 달기에 불과했다 : 지금 거기 입니다 같은 기능. 나머지 제안서 (리팩터링 +을 사용하여 리팩토링 )는 슬프게도 Java 개발자가 기꺼이 바꾸려는 것 이상을 요구합니다.
Ti Strga

@TiStrga : Java 바이트 코드 파일이 "함수 X를 사용할 수 있으면 호출하십시오. Java의 정적 메소드에 연결하거나 사용 불가능한 경우 stringbuilder를 사용할 수있는 정적 메소드를 사용하여 코드를 생성하면 최적의 솔루션으로 보입니다.
supercat

22

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

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

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

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

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


"이 임시 작업"에 따르면 이스케이프 분석을 사용하여 가능한 올바른 스택에 "힙"개체를 할당해야합니다. 탈출 분석 핫스팟에서 (일부 동기화를 제거하는데 유용) 존재하지만, 나는 그것을 믿지 않는다, 글을 쓰는 시점에있다, U
톰 Hawtin의 - tackline

21

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

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"코드에서 문자열 생성을 최적화 할 수 없었던 이유는 무엇입니까? 새로운 문자열 생성을 피할 수 있습니다. 위의 진술을 믿지 않으면 스스로 테스트하십시오.


Java jdk1.8.0_241에서 코드를 테스트했습니다. 저에게 "a + b"코드는 최적화 된 결과를 제공합니다. : CONCAT ()와 203ms 와 "+"로 : 113ms . 이전 릴리스에서는 최적화되지 않은 것 같습니다.
Akki

6

기본적으로 +와 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 방법 의 기본적인 차이점 입니다. 즐겨 :)


친애하는, 당신은 문자열 풀에 저장하는 문자열 객체 자체로 취급되는 모든 문자열 리터럴을 잘 알고 있습니다.이 경우 우리는 4 개의 문자열 리터럴을 가지고 있으므로 풀에서 적어도 4 개의 객체를 만들어야합니다.
Deepak Sharma

1
나는 그렇게 생각하지 않습니다 : String s="I"+"am"+"good"+"boy"; String s2 = "go".concat("od"); System.out.println(s2 == s2.intern());prints true, "good"전화하기 전에 문자열 풀에 없었습니다.intern()
fabian

이 줄에 대해서만 이야기하고 있습니다 String s = "I"+ "am"+ "good"+ "boy"; 이 경우 모두 4 개의 문자열 리터럴이 풀에 유지되므로 풀에 4 개의 오브젝트를 작성해야합니다.
Deepak Sharma

4

완벽을 기하기 위해 JLS SE8 15.18.1 에서 '+'연산자의 정의를 찾을 수 있다고 덧붙이고 싶습니다 .

하나의 피연산자 식만 String 유형 인 경우 런타임시 문자열을 생성하기 위해 다른 피연산자에 대해 문자열 변환 (§5.1.11)이 수행됩니다.

문자열 연결의 결과는 두 피연산자 문자열의 연결 인 String 객체에 대한 참조입니다. 왼쪽 피연산자의 문자는 새로 작성된 문자열에서 오른쪽 피연산자의 문자보다 우선합니다.

표현식이 상수 표현식 (§15.28)이 아니면 String 객체가 새로 생성됩니다 (§12.5).

구현에 대해 JLS는 다음과 같이 말합니다.

구현은 중간 String 객체를 생성 한 다음 폐기하지 않도록 한 단계에서 변환 및 연결을 수행하도록 선택할 수 있습니다. 반복되는 문자열 연결의 성능을 높이기 위해 Java 컴파일러는 StringBuffer 클래스 또는 유사한 기술을 사용하여 표현식 평가에 의해 생성 된 중간 String 객체의 수를 줄일 수 있습니다.

프리미티브 유형의 경우 구현시 기본 유형에서 문자열로 직접 변환하여 랩퍼 오브젝트 작성을 최적화 할 수도 있습니다.

따라서 'Java 컴파일러는 StringBuffer 클래스 또는 유사한 기술을 사용하여 줄일 수 있습니다'로 판단하면 다른 컴파일러는 다른 바이트 코드를 생성 할 수 있습니다.


2

+ 연산자는 문자열, 문자열, 문자, 정수, 부동 소수점 또는 더블 데이터 타입 값 사이 일 수있다. 연결하기 전에 값을 문자열 표현으로 변환합니다.

CONCAT 연산자는 단지와 문자열을 수행 할 수 있습니다. 데이터 형식 호환성을 확인하고 일치하지 않으면 오류를 발생시킵니다.

이를 제외하고 제공 한 코드는 동일한 작업을 수행합니다.


2

나는 그렇게 생각하지 않습니다.

a.concat(b)String으로 구현되었으며 초기 Java 시스템 이후로 구현이 크게 변경되지 않았다고 생각합니다. +작업을 구현 자바 버전과 컴파일러에 따라 달라집니다. 현재 가능한 빨리 작업을 수행하기 위해 +사용 StringBuffer됩니다. 아마도 미래에는 이것이 바뀔 것입니다. 이전 버전의 +문자열에서 Java 작업은 중간 결과를 생성하므로 속도가 훨씬 느 렸습니다.

나는 그것을 +=사용 +하여 유사하게 최적화 된 것 같아요 .


7
"현재 +는 StringBuffer를 사용하여 구현됩니다"False StringBuilder입니다. StringBuffer는 StringBuilder의 스레드 안전 임프입니다.
Frederic Morin

1
StringBuilder가 처음 소개되었을 때의 버전이므로 Java 1.5 이전의 StringBuffer였습니다.
ccpizza

0

+를 사용하면 문자열 길이가 증가함에 따라 속도가 감소하지만 concat을 사용하면 속도가 더 안정적이며 가장 좋은 방법은 속도가 안정적인 StringBuilder 클래스를 사용하는 것입니다.

이유를 이해할 수있을 것 같아요. 그러나 긴 문자열을 만드는 가장 좋은 방법은 StringBuilder () 및 append ()를 사용하는 것입니다. 속도는 허용되지 않습니다.


1
+ 연산자를 사용하는 것은 StringBuilder를 사용하는 것과 같습니다 ( docs.oracle.com/javase/specs/jls/se8/html/… )
ihebiheb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.