문제

문자열 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 동안의 가치 + 연산자는 자동으로 인수를 문자열로 변환합니다( 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 적어도 성능 측면에서는 방법이 승리합니다.

소스 코드 String 그리고 StringBuilder (및 해당 package-private 기본 클래스)은 Sun JDK의 src.zip에서 사용할 수 있습니다.char 배열을 구축하고(필요에 따라 크기 조정) 최종 배열을 생성할 때 이를 버리는 것을 볼 수 있습니다. String.실제로 메모리 할당은 놀라울 정도로 빠릅니다.

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

다른 팁

니야즈 맞습니다. 그러나 특수 + 연산자가 Java 컴파일러에 의해 더 효율적인 연산자로 변환될 수 있다는 점도 주목할 가치가 있습니다.Java에는 스레드로부터 안전하지 않고 변경 가능한 문자열을 나타내는 StringBuilder 클래스가 있습니다.여러 문자열 연결을 수행할 때 Java 컴파일러는 자동으로 변환합니다.

String a = b + c + d;

~ 안으로

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

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

그러나 빈 문자열을 기존 문자열에 연결할 때는 concat 메서드가 더 효율적입니다.이 경우 JVM은 새 String 객체를 생성할 필요가 없으며 단순히 기존 객체를 반환하면 됩니다.보다 연결 문서 이것을 확인하기 위해.

따라서 효율성이 매우 중요하다면 비어 있을 가능성이 있는 문자열을 연결할 때는 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회 실행되었으며, 각 실행당 100,000회 반복이 이루어졌습니다.결과는 다음과 같습니다.

  • StringBuilder 손에 쥐고 승리합니다.대부분의 실행에서 시계 시간 결과는 0이었으며 가장 긴 시간은 16ms가 걸렸습니다.
  • a += b 각 실행에 약 40000ms(40초)가 소요됩니다.
  • concat 실행당 10000ms(10초)만 필요합니다.

아직 내부를 보거나 프로파일러를 통해 실행하기 위해 클래스를 디컴파일하지 않았지만 의심스럽습니다. 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 런타임 최적화의 효과를 무시하고 있습니다.특히 이러한 임시 작업은 일반적인 패턴으로 인식되며 런타임 시 보다 효율적인 기계어 코드로 대체됩니다.

@마르시오:당신은 마이크로 벤치마크;최신 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" 코드에서 문자열 생성을 최적화할 수 없는 이유는 무엇입니까?새로운 문자열 생성을 피할 수 있습니다.위의 설명을 믿을 수 없다면 스스로 테스트해 보세요.

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

  1. 당신이 사용하는 경우 연결 방법을 사용하면 문자열을 연결할 수만 있습니다. + 연산자를 사용하면 문자열을 모든 데이터 유형과 연결할 수도 있습니다.

    예를 들어:

    String s = 10 + "Hello";
    

    이 경우 출력은 다음과 같아야 합니다. 10안녕하세요.

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

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

  2. 두 번째이자 주요 차이점 + 그리고 연결 그것은:

    사례 1:동일한 문자열을 다음과 연결한다고 가정합니다. 연결 운영자는 이런 식으로

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

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

    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개의 객체만 생성됩니다.

그래서 여러분, 이것이 다음과 같은 기본적인 차이점입니다. + 그리고 연결 방법.즐기다 :)

완전성을 기하기 위해 '+' 연산자의 정의는 JLS SE8 15.18.1:

하나의 피연산자 표현 만 유형 인 경우, 다른 피연산자에서 문자열 변환 (§5.1.11)이 수행되어 런 타임에 문자열을 생성합니다.

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

표현이 일정한 표현이 아닌 한 문자열 객체는 새로 만들어졌습니다 (§12.5) (§15.28).

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

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

원시 유형의 경우, 구현은 원시 유형에서 문자열로 직접 변환하여 래퍼 객체의 생성을 최적화 할 수 있습니다.

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

그만큼 + 연산자 문자열과 문자열, char, 정수, double 또는 float 데이터 유형 값 사이에서 작업할 수 있습니다.연결하기 전에 값을 문자열 표현으로 변환합니다.

그만큼 연결 연산자 문자열에서만 수행할 수 있습니다.데이터 유형 호환성을 확인하고 일치하지 않으면 오류가 발생합니다.

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

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

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

내가 생각 하기엔 += 을 사용하여 구현됩니다 + 마찬가지로 최적화되었습니다.

+를 사용하면 문자열의 길이가 길어질수록 속도가 감소하지만 concat을 사용하면 속도가 더 안정적이게 되므로 가장 좋은 방법은 안정적인 속도를 갖는 StringBuilder 클래스를 사용하는 것입니다.

그 이유를 이해하실 수 있을 것 같습니다.그러나 긴 문자열을 생성하는 가장 좋은 방법은 StringBuilder() 및 Append()를 사용하는 것입니다. 두 가지 모두 속도가 허용되지 않습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top