본문 바로가기
Java

반복문안의 문자열 연결을 하는 경우 어떤 방법을 사용해야 할까?

by greeng00se 2022. 10. 27.

개요

  • String 클래스는 문자열을 다룰 때 사용된다.
  • 문자열 연결의 경우 기본적으로 + 연산자를 사용하여 문자열을 연결한다.
  • 좋은 방법인지? 또한 더 좋은 방법은 없는지 알아보자

JDK 1.5 이후의 문자열 연결

  • JDK 1.5 이후 문자열 연결 연산자의 경우 컴파일 과정에서 StringBuilder와 append 메서드를 사용하도록 최적화를 해준다.

The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method. String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java.

그럼 무조건 + 연산자를 사용하는 것이 좋을까?

  • 이펙티브 자바 Item 63에서는 문자열 연결의 경우 느리기 때문에 주의해서 사용하라고 되어있다.
  • 작은 문자열의 경우 문제가 없지만 긴 문자열을 연결하는 경우 n^2의 시간에 비례한다고 되어있다.
  • 이는 String 자체가 불변이기 때문에 연결해야 하는 문자열과 기존의 문자열 둘 다 복사해야 하기 때문이다.
  • 따라서 많은 양의 문자열을 합치는 경우 StringBuilder를 사용하는 것이 더 좋다고 한다.

반복문의 문자열 연결

@Test
void concat() {
    String result = "";

    for(int i = 0; i < 1000; ++i) {
        result = result + i;
    }

}

@Test
void concatWithStringBuilder() {
    StringBuilder sb = new StringBuilder();

    for(int i = 0; i < 1000; ++i) {
        sb.append(i);
    }

}
  • 정말 문자열 연결이 최적화되는지 javap -c 를 이용하여 클래스 파일을 역어셈블하여 확인해보자

JDK8 - 반복문의 문자열 연결

void concat();
  Code:
     0: ldc           #7                  // String
     2: astore_1
     3: iconst_0
     4: istore_2
     5: iload_2
     6: sipush        1000
     9: if_icmpge     37
    12: new           #9                  // class java/lang/StringBuilder
    15: dup
    16: invokespecial #11                 // Method java/lang/StringBuilder."<init>":()V
    19: aload_1
    20: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    23: iload_2
    24: invokevirtual #16                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    27: invokevirtual #19                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    30: astore_1
    31: iinc          2, 1
    34: goto          5
    37: return

void concatWithStringBuilder();
  Code:
     0: new           #9                  // class java/lang/StringBuilder
     3: dup
     4: invokespecial #11                 // Method java/lang/StringBuilder."<init>":()V
     7: astore_1
     8: iconst_0
     9: istore_2
    10: iload_2
    11: sipush        1000
    14: if_icmpge     29
    17: aload_1
    18: iload_2
    19: invokevirtual #16                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    22: pop
    23: iinc          2, 1
    26: goto          10
    29: return
    • 연산자의 사용한 경우 StringBuilder를 반복하여 생성하는 것을 볼 수 있다.
      • goto 문으로 code 5로 가기 때문에 결국 1000번 StringBuilder를 생성하고, 삭제된다.
  • StringBuilder를 사용한 경우 한 번만 생성하고 append 메서드를 1000번 호출한다.

JDK 11 - 반복문 안에서의 문자열 연결

void concat();
  Code:
     0: ldc           #7                  // String
     2: astore_1
     3: iconst_0
     4: istore_2
     5: iload_2
     6: sipush        1000
     9: if_icmpge     26
    12: aload_1
    13: iload_2
    14: invokedynamic #9,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
    19: astore_1
    20: iinc          2, 1
    23: goto          5
    26: return

void concatWithStringBuilder();
  Code:
     0: new           #13                 // class java/lang/StringBuilder
     3: dup
     4: invokespecial #15                 // Method java/lang/StringBuilder."<init>":()V
     7: astore_1
     8: iconst_0
     9: istore_2
    10: iload_2
    11: sipush        1000
    14: if_icmpge     29
    17: aload_1
    18: iload_2
    19: invokevirtual #16                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    22: pop
    23: iinc          2, 1
    26: goto          10
    29: return
  • StringBuilder를 사용한 경우 jdk 1.8과 다른 점이 없어보이지만 + 연산자를 사용한 경우 다르게 동작한다.

In the presentation "Enough java.lang.String to Hang Ourselves ...," Dr. Heinz M. Kabutz and Dmitry Vyazelenko discuss the JEP 280-introduced changes to Java string concatenation and summarize it succinctly, "+ is no longer compiled to StringBuilder." In their "Lessons from Today" slide, they state, "Use + instead of StringBuilder where possible" and "recompile classes for Java 9+."

The changes implemented in JDK 9 for JEP 280 "will enable future optimizations of String concatenation without requiring further changes to the bytecode emitted by javac." Interestingly

  • Java 9 이후로 이미 컴파일된 코드라도 JDK 버전 업이 되었을 때 최적화가 되도록 변경된 것으로 보인다.
  • 실제로 Java 11기준 invokedynamic으로 호출되는 StringConcatFactory의 코드를 보면 내부는 StringBuilder를 사용하는 것으로 보인다.

결론

  • 실제로 Java 9 이후에 최적화가 잘 되었지만 그래도 문자열을 10000번 이상 반복하여 연결하는 경우 유의미한 차이를 보인다.
  • 한 줄짜리 연결 코드의 경우 그냥 사용해도 문제가 없지만 반복하는 횟수가 높아질수록 StringBuilder를 사용하는 것이 좋아 보인다.
  • 추가로 멀티스레드 환경에서 동기화가 필요한 경우 StringBuffer를 사용해야 한다.

참고 자료