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

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?



답변

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

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

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


답변

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

예를 들어 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();


답변

나는 선호한다:

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

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

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

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


답변

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

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

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

그래서-당신의 질문에 :

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


답변

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


답변

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

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

2 예외 :

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

답변

최신 버전의 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루프로 사용하는 동안 실제로 필요하지 않은 중간 문자열을 생성하고 있습니다 .