Java에서 toString ()의 StringBuilder 및 문자열 연결


925

toString()아래 두 가지 구현을 고려할 때 어느 것이 선호됩니까?

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

또는

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

더 중요한 것은 3 개의 속성 만 있으면 차이가 없을 수 있지만 어느 시점에서 +concat에서 StringBuilder?


40
어떤 시점에서 StringBuilder로 전환합니까? 메모리 나 성능에 영향을 줄 때 아니면 때. 실제로 두 줄로 한 번만 이렇게하면 걱정할 필요가 없습니다. 그러나 계속해서 반복한다면 StringBuilder를 사용할 때 상당한 차이가 나타납니다.
ewall

모수에서 평균 100은 얼마입니까?
Asif Mushtaq

2
100 @UnKnown 모두 StringBuilder의 초기 크기
비 sequitor는

@nonsequitor 최대 문자는 100입니까?
Asif Mushtaq

10
@Unknown은 초기 크기뿐만 아니라 처리하는 문자열의 대략적인 크기를 알고 있다면 StringBuilder미리 할당 할 크기를 알 수 있습니다. 그렇지 않으면 공간이 부족한 경우 크기를 두 배로 늘려야합니다. 새 char[]배열은 데이터를 복사합니다. 비용이 많이 듭니다. 크기를 지정하여 속임수를 쓸 수 있으며이 배열 생성이 필요하지 않습니다. 따라서 문자열이 ~ 100 자라고 생각하면 StringBuilder를 해당 크기로 설정할 수 있으며 내부적으로 확장 할 필요가 없습니다.
non sequitor

답변:


964

버전 1은 더 짧기 때문에 바람직 하며 컴파일러는 실제로 버전 2로 변환하므로 성능 차이는 없습니다.

더 중요한 것은 우리에게 단지 3 개의 속성 만 있다면 차이가 없을 수 있지만, concat에서 builder로 어느 시점에서 전환합니까?

루프에서 연결하는 시점에서-일반적으로 컴파일러 StringBuilder가 자체적으로 대체 할 수없는 경우 입니다.


19
사실이지만 언어 참조는 이것이 선택 사항이라고 말합니다. 실제로 JRE 1.6.0_15로 간단한 테스트를했는데 디 컴파일 된 클래스에서 컴파일러 최적화를 보지 못했습니다.
bruno conde

37
방금 질문에서 코드를 시도하고 (JDK 1.6.0_16에서 컴파일) 예상대로 최적화를 찾았습니다. 모든 현대 컴파일러가 그렇게 할 것이라고 확신합니다.
Michael Borgwardt

22
당신이 올바른지. 바이트 코드를 보면 StringBuilder 최적화를 명확하게 볼 수 있습니다. 나는 디 컴파일러를 사용하고 있었고, 어떻게하면 concat으로 다시 변환하고 있습니다. +1
bruno conde

80
아니 죽은 말을 이길하지만 사양의 표현은 할 수 : To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.핵심 단어가 거기 할 수있다 . 이것이 공식적으로 선택적인 것이라면 (우리는 아마도 가장 가능성이 높지만) 우리 자신을 보호하지 말아야합니까?
루카스

93
@Lucas : 아닙니다. 컴파일러가 해당 최적화를 수행하지 않기로 결정하면 가치가 없기 때문입니다. 99 %의 경우 컴파일러는 가치가있는 최적화가 무엇인지 잘 알고 있으므로 일반적으로 개발자는 방해하지 않아야합니다. 물론 상황 다른 1 %에 해당 할 수도 있지만 (주의해서) 벤치마킹을 통해서만 확인할 수 있습니다.
sleske

255

핵심은 단일 연결을 한 곳에 작성하거나 시간이 지남에 따라 누적하는 것입니다.

예를 들어 StringBuilder를 명시 적으로 사용하는 것은 중요하지 않습니다. (첫 번째 경우의 컴파일 된 코드를보십시오.)

그러나 루프 내부에서 문자열을 작성하는 경우 StringBuilder를 사용하십시오.

분명히 설명하기 위해 hugeArray에 수천 개의 문자열이 포함되어 있다고 가정하면 다음과 같은 코드가 작성됩니다.

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

다음과 비교할 때 시간과 메모리가 낭비됩니다.

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

3
예, StringBuilder는 String 객체를 반복해서 다시 만들 필요가 없습니다.
Olga

124
. 젠장 내가 11secs 대 6.51min에서 일하고 있어요 큰 문자열을 테스트하는이 기능을 사용
user1722791

1
그건 그렇고 당신도 사용할 수 있습니다 result += s;(첫 번째 예에서)
RAnders00

1
이 문장으로 몇 개의 객체가 생성됩니까? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
Asif Mushtaq

1
String str = (a == null)? null : a '+ (b == null)? null : b '+ (c == null)? c : c '+ ...; ? 최적화가 일어나지 못하게됩니까?
amitfr

75

나는 선호한다:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... 짧고 읽을 수 있기 때문입니다.

내가 것 없는 당신은 매우 높은 반복 횟수와 루프 내부에 그것을 사용하지 않는 한 속도에 대해이 작업을 최적화 하고 성능 차이를 측정했다.

많은 매개 변수를 출력 해야하는 경우이 양식이 혼란 스러울 수 있음에 동의합니다 (의견 중 하나가 말한 것처럼). 이 경우 더 읽기 쉬운 형식으로 전환하고 ( 아파치 커먼즈의 ToStringBuilder 를 사용하여 매트 b의 대답에서 가져옴) 성능을 다시 무시합니다.


64
실제로 더 길고 더 많은 기호를 포함하며 텍스트에 순서가 다른 변수가 있습니다.
Tom Hawtin-tackline

4
그렇다면 다른 공격 중 하나보다 읽기 쉽지 않다고 말할 수 있습니까?
tangens

3
더 많은 변수를 추가하는 것이 더 쉽기 때문에 이것을 작성하는 것을 선호하지만 더 많은 인수를 읽을 수 있는지 확실하지 않습니다. 특히 인수 수가 클수록 더 읽기 쉽습니다. 다른 시간에 비트를 추가 해야하는 경우에도 작동하지 않습니다.
Alex Feinman

78
읽기가 더 어려워 보입니다. 이제 {...}와 매개 변수 사이에서 앞뒤로 스캔해야합니다.
Steve Kuo

10
매개 변수 중 하나가 다음과 같은 경우 안전 null
하므로이

74

대부분의 경우 두 접근 방식간에 실제 차이는 보이지 않지만 다음과 같은 최악의 시나리오를 구성하는 것은 쉽습니다.

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}

출력은 다음과 같습니다.

slow elapsed 11741 ms
fast elapsed 7 ms

문제는 문자열에 + =를 추가하면 새 문자열을 재구성하므로 문자열 길이에 비례하는 비용이 든다는 것입니다 (둘 다 합산).

그래서-당신의 질문에 :

두 번째 방법은 더 빠르지 만 읽기 어렵고 유지하기가 더 어렵습니다. 내가 말했듯이, 특정 경우에는 아마도 차이가 보이지 않을 것입니다.


.concat ()을 잊지 마십시오. 경과 시간이 10에서 18ms 사이로 예상되어 원래 포스트 예제와 같은 짧은 문자열을 사용할 때 무시할 수 있습니다.
Droo

9
에 대해 옳았지만 +=원래 예제는의 시퀀스이며 +컴파일러는 단일 string.concat호출로 변환합니다 . 결과가 적용되지 않습니다.
Blindy

1
@Blindy & Droo :-둘 다 맞습니다. 이러한 시나리오에서 .concate를 사용하는 것이 가장 좋은 해결 방법입니다. + = 루프 루틴이 실행될 때마다 새 객체를 만듭니다.
perilbrain

3
그의 toString ()이 루프에서 호출되지 않는다는 것을 알고 있습니까?
Omry Yadan

속도를 테스트하기 위해이 예제를 시도했습니다. 내 결과는 다음과 같습니다. 느리게 경과 된 29672 ms; 빠른 경과 시간은 15ms입니다. 따라서 그 대답은 분명합니다. 그러나 100 반복이라면 시간은 같습니다-0 ms. 500 회 반복-16ms 및 0ms 등등.
Ernestas Gruodis

28

또한 append 또는 +를 사용할 지에 대한 사실과 상사와 충돌했습니다 .Append를 사용하고 있기 때문에 (새로운 객체가 생성 될 때마다 말하는 것처럼 여전히 파악할 수 없습니다). 그래서 저는 R & D를 할 생각을했지만 Michael Borgwardt의 설명을 좋아하지만 누군가가 나중에 정말로 알아야 할 경우 설명을 보여주고 싶었습니다.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

위 클래스의 분해는 다음과 같이 나옵니다.

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

위의 두 코드에서 Michael이 옳다는 것을 알 수 있습니다. 각각 하나의 SB 객체 만 생성됩니다.


27

Java 1.5부터 "+"및 StringBuilder.append ()를 사용한 간단한 한 줄 연결은 정확히 동일한 바이트 코드를 생성합니다.

코드 가독성을 위해 "+"를 사용하십시오.

2 예외 :

  • 멀티 스레드 환경 : StringBuffer
  • 루프 연결 : StringBuilder / StringBuffer

22

최신 버전의 Java (1.8)를 사용하는 disassembly ( javap -c)는 컴파일러가 도입 한 최적화를 보여줍니다. +또한 sb.append()매우 유사한 코드를 생성합니다. 그러나 우리가 사용하는 경우 동작을 검사하는 것이 좋습니다.+ for 루프에서 하는 .

for 루프에서 +를 사용하여 문자열 추가

자바:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

바이트 코드 :( for루프 발췌)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

stringbuilder.append를 사용하여 문자열 추가

자바:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

ByteCdoe :( for루프 발췌)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

그래도 띄는 차이 가 있습니다. 사용 된 첫 번째 경우에는 루프 반복마다 +새로 StringBuilder작성되고 생성 된 결과는 toString()호출 (29-41)을 수행하여 저장됩니다 . 따라서 +연산자를 for루프로 사용하는 동안 실제로 필요하지 않은 중간 문자열을 생성하고 있습니다 .


1
이 Oracle JDK 또는 OpenJDK입니까?
Christophe Roussy

12

Java 9에서 버전 1은 invokedynamic호출 로 변환되므로 더 빨라야 합니다. 자세한 내용은 JEP-280 에서 찾을 수 있습니다 .

아이디어는 전체 StringBuilder 추가 댄스를 java.lang.invoke.StringConcatFactory에 대한 간단한 invokedynamic 호출로 대체하여 연결이 필요한 값을 허용하는 것입니다.


9

성능상의 이유로 +=( String연결)을 사용하지 않는 것이 좋습니다. 그 이유는 다음과 같습니다. Java String는 불변입니다. 새로운 연결이 완료 될 때마다 새로운 String것이 생성됩니다 ( 새 문자열은 이미 문자열 풀에있는 이전 지문과 다른 지문을 가짐) ). 새 문자열을 만들면 GC에 압력이 가해져 프로그램 속도가 느려집니다. 개체 생성 비용이 많이 듭니다.

아래 코드는 더 실용적이고 명확하게 만들어야합니다.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

실행 결과는 다음과 같습니다.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

1 개의 연결 (JIT가 아직 작업을 수행하지 않음)에 대한 결과를 고려하지 않고 10 개의 연결에도 성능 불이익이 관련됩니다. 수천 개의 연결의 경우 그 차이가 큽니다.

이 매우 빠른 실험에서 얻은 교훈 (위의 코드로 쉽게 재현 가능) : +=몇 가지 연결이 필요한 매우 기본적인 경우에도 문자열을 연결 하는 데 절대로 사용하지 마십시오 . GC).


9

아래 예를 참조하십시오.

static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) {
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    System.out.println("[str1.concat(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case2() {
    System.out.println("[str1+=str2]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str += UUID.randomUUID() + "---";
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case3() {
    System.out.println("[str1.append(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
    executionTimes.add(System.currentTimeMillis() - startTime);
    if (executionTimes.size() % CALC_AVG_EVERY == 0) {
        out.println("average time for " + executionTimes.size() + " concatenations: "
                + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                + " ms avg");
        executionTimes.clear();
    }
}

산출:

java 바이트 코드 버전 : 8
java.version : 1.8.0_144
[str1.concat (str2)]
10000 연결의 평균 시간 : 10000 연결의 평균
평균 시간 : 10000 연결의 평균
평균 시간 : 10000 연결의 평균
평균 시간 : 0.327 ms의 평균 평균 시간 10000 회씩 연결 : 0.501 밀리 평균
10000 회씩 연결 평균 시간 : 0.656 MS가 및 평균
길이의 문자열을 만든 : 1950000에서 17,745 MS
[str1과 + = STR2]
평균 시간을 10000 회씩 연결을 위해 : 0.21 MS는 평균
10,000 회씩 연결 평균 시간 : 0.652 MS는 평균
평균 시간 10000 연결 : 평균 1.129ms 평균
10000 연결 : 평균 1.727ms
10000 연결의 평균 시간 : 평균 2.302ms 60279ms
10000 연결에 대한 [str1.append (str2)] 평균 시간의 문자열 : 1950000 생성 : 10000 연결에 대한 평균 평균 시간 : 0.002 ms 평균 10000 연결에 대한 평균 평균 시간 : 10000 연결에 대한 평균 평균 시간 : 0.0000 ms 10000 연결에 대한 평균 평균 시간 : 0.002 ms 10000 연결의 평균 평균 시간 : 0.002ms 평균 길이의 문자열 : 100ms 에서 1950000






문자열 길이가 길수록 연결 시간도 늘어납니다.
그것이 StringBuilder꼭 필요한 곳입니다.
보시다시피 연결 : UUID.randomUUID()+"---"은 실제로 시간에 영향을 미치지 않습니다.

추신 : Java에서 언제 StringBuilder를 사용할 지는 실제로 이것의 복제본 이라고 생각하지 않습니다 .
이 질문은 toString()대부분의 시간이 거대한 줄을 연결하지 않는 것에 대해 이야기 합니다.


2019 년 업데이트

이후 java8시간, 상황이 조금 변경되었습니다. 이제 (java13)의 연결 시간 +=은 실제로와 같습니다 str.concat(). 그러나 StringBuilder연결 시간은 여전히 ​​일정 합니다. (위의 원본 게시물은 더 자세한 출력을 추가하기 위해 약간 편집되었습니다)

자바 바이트 코드 버전 : 13
java.version : 13.0.1
[str1.concat (STR2)]
10000 회씩 평균 시간 : 0.047 MS는 평균
10000 회씩 평균 시간이 0.1 밀리 초를 평균
10000 회씩 평균 시간 : 0.17 MS는 평균
의 평균 시간 10000 연결 :
평균 10000 연결의 평균 시간 : 0.255 ms avg 평균
길이 : 0.336 ms 평균 생성 된 문자열 : 9147 ms 에서 1950000
[str1 + = str2]
10000 연결의
평균 시간 : 0.000 ms 평균 : 10000 연결의 평균 시간 : 0.097 ms 평균
평균 시간 10000 연결 : 평균 0.249ms
평균 시간 10000 연결 : 평균 0.298ms
10000 연결의 평균 시간 : 평균 0.326 ms 10191 ms
문자열 : 1950000 생성 :

10000 연결에 대한 [str1.append (str2)] 평균 시간 : 10000 연결에 대한 평균
평균 시간 : 0.001 ms 평균
10000 연결에 대한 평균
평균 시간 : 10000 연결에 대한 평균 시간 : 0.001 ms 평균
10000 연결에 대한 평균 시간 : 10000 연결에 대한 평균 평균 시간 : 0.001 ms 0.001 밀리 및 평균
에 1,950,000 : 길이의 문자열을 만든 43 밀리

주목할만한 bytecode:8/java.version:13조합은 다음과 비교할 때 성능상의 이점이 있습니다.bytecode:8/java.version:8


이것은 정답입니다.. concat 또는 StringBuilder의 선택을 결정하는 String Stream의 크기에 따라 다릅니다.
user1428716

@ user1428716 참고 : 답변 java13이 다른 결과로 업데이트되었습니다 . 그러나 나는 주요 결론이 동일하다고 생각합니다.
Marinos An

7

Apache Commons-Lang에는 사용하기 매우 쉬운 ToStringBuilder 클래스가 있습니다. 그것은 당신이 toString을 어떻게 보이길 원하는지에 대한 서식 지정뿐만 아니라 append-logic을 처리하는 훌륭한 일을합니다.

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

다음과 같은 출력을 반환합니다 com.blah.YourClass@abc1321f[a=whatever, b=foo] .

또는 체인을 사용하여 더 간결한 형태로 :

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

또는 리플렉션을 사용하여 클래스의 모든 필드를 포함하려는 경우 :

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

원하는 경우 ToString 스타일을 사용자 정의 할 수도 있습니다.


4

toString 메소드를 가능한 한 읽기 쉽게 작성하십시오!

내 책에서 이것에 대한 유일한 예외는 당신이 증명할 수 있다면 이 나에게 그것이 많은 자원을 소비한다는 :) (예, 이것은 프로파일 링을 의미합니다)

또한 Java 5 컴파일러는 이전 버전의 Java에서 사용 된 필기 "StringBuffer"방식보다 빠른 코드를 생성합니다. "+"를 사용하면 향후 개선 사항이 무료로 제공됩니다.


3

현재 컴파일러에서 StringBuilder 사용이 여전히 필요한지에 대한 논쟁이있는 것 같습니다. 그래서 나는 2 센트의 경험을 줄 것이라고 생각했다.

JDBC10k 레코드 의 결과 세트가 있습니다 (예, 모두 하나의 배치로 필요합니다). + 연산자를 사용하면 내 컴퓨터에서 약 5 분이 소요됩니다 Java 1.8. 사용stringBuilder.append("")동일한 쿼리에 데 1 초도 걸리지 않습니다.

차이가 큽니다. 루프 내부 StringBuilder가 훨씬 빠릅니다.


2
토론은 루프 외부에서 사용하는 것에 관한 것입니다. 루프 안에서 사용해야 할 합의가 있다고 생각합니다.
Jordan

2

'+'를 사용하는 성능 현명한 문자열 연결은 문자열이 Java에서 변경할 수 없으므로 완전히 새로운 문자열의 사본을 만들어야하므로 비용이 많이 듭니다. 연결이 매우 빈번한 경우 (예 : 루프 내부)에 특히 중요한 역할을합니다. 다음은 그러한 일을 시도 할 때 IDEA가 제안하는 것입니다.

여기에 이미지 설명을 입력하십시오

일반 규칙:

  • 단일 문자열 할당 내에서 문자열 연결을 사용하는 것이 좋습니다.
  • 큰 문자 데이터 블록을 만들기 위해 반복하는 경우 StringBuffer로 이동하십시오.
  • String에서 + =를 사용하는 것은 StringBuffer를 사용하는 것보다 항상 덜 효율적이므로 경고 벨을 울려 야합니다. 그러나 어떤 경우에는 가독성 문제와 비교하여 얻은 최적화가 무시할 수 있으므로 상식을 사용하십시오.

이 주제에 관한 멋진 Jon Skeet 블로그가 있습니다.


2
여러 스레드에서 동기화 된 액세스가 절대 필요한 경우가 아니면 StringBuffer를 사용해서는 안됩니다. 그렇지 않으면 동기화되지 않아 오버 헤드가 적은 StringBuilder를 선호합니다.
Erki der Loony 19

1

컬렉션을 반복하고 StringBuilder를 사용하려는 경우 Apache Commons LangStringUtils.join () 을 확인하고 싶을 수 있습니다. (다른 풍미) 있습니까?

성능에 관계없이 StringBuilders를 작성하고 백만 번째 시간 처럼 보이는 루프를 작성하지 않아도됩니다 .


1

Java8에서 확인한 내용은 다음과 같습니다.

  • 문자열 연결 사용
  • StringBuilder 사용

    long time1 = System.currentTimeMillis();
    usingStringConcatenation(100000);
    System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms");
    
    time1 = System.currentTimeMillis();
    usingStringBuilder(100000);
    System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms");
    
    
    private static void usingStringBuilder(int n)
    {
        StringBuilder str = new StringBuilder();
        for(int i=0;i<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }

많은 수의 문자열에 문자열 연결을 사용하는 경우 실제로 악몽입니다.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

-1

StringBuilder 추가 접근법을 사용해야한다고 생각합니다. 이유 :

  1. String 연결은 매번 새로운 문자열 객체를 생성하므로 (String은 불변 객체이므로) 3 개의 객체를 생성합니다.

  2. 문자열 빌더를 사용하면 하나의 오브젝트 만 작성되고 [StringBuilder는 변경 가능] 추가 문자열이 추가됩니다.


이 답변이 다운 보트 인 이유는 무엇입니까? docs.oracle.com/javase/8/docs/api/java/util/stream/…- 가변 감소
user1428716

-4

그런 간단한 문자열을 선호합니다

"string".concat("string").concat("string");

순서대로 문자열을 구성하는 기본 방법은 StringBuilder, String # concat (), 오버로드 + 연산자를 사용하는 것입니다. StringBuilder는 + 연산자를 사용하는 것처럼 큰 문자열을 작업 할 때 성능이 크게 저하되는 것처럼 (문자열 크기가 증가함에 따라 기하 급수적으로) 크게 성능이 향상됩니다. .concat ()을 사용할 때의 한 가지 문제는 NullPointerExceptions을 던질 수 있다는 것입니다.


9
JLS에서 '+'를 StringBuilder로 변환 할 수 있기 때문에 concat ()을 사용하면 '+'보다 성능이 저하 될 가능성이 높으며 대부분의 JVM이 그렇게하거나보다 효율적인 대안을 사용할 수 있습니다. 예제에서 하나 이상의 완전한 중간 문자열을 작성하여 버려야하는 concat.
Lawrence Dol
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.