개요
- 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
(orStringBuffer
) class and itsappend
method. String conversions are implemented through the methodtoString
, defined byObject
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
를 생성하고, 삭제된다.
- goto 문으로 code 5로 가기 때문에 결국 1000번
- 연산자의 사용한 경우
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 toStringBuilder
." In their "Lessons from Today" slide, they state, "Use+
instead ofStringBuilder
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 byjavac
." Interestingly
- Java 9 이후로 이미 컴파일된 코드라도 JDK 버전 업이 되었을 때 최적화가 되도록 변경된 것으로 보인다.
- 실제로 Java 11기준
invokedynamic
으로 호출되는StringConcatFactory
의 코드를 보면 내부는StringBuilder
를 사용하는 것으로 보인다.
결론
- 실제로 Java 9 이후에 최적화가 잘 되었지만 그래도 문자열을 10000번 이상 반복하여 연결하는 경우 유의미한 차이를 보인다.
- 한 줄짜리 연결 코드의 경우 그냥 사용해도 문제가 없지만 반복하는 횟수가 높아질수록
StringBuilder
를 사용하는 것이 좋아 보인다. - 추가로 멀티스레드 환경에서 동기화가 필요한 경우
StringBuffer
를 사용해야 한다.
참고 자료
- https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/String.html
- https://gist.github.com/benelog/b81b4434fb8f2220cd0e900be1634753
- https://tecoble.techcourse.co.kr/post/2020-06-15-String-vs-StringBuilder-vs-StringBuffer/
- https://dzone.com/articles/jdk-9jep-280-string-concatenations-will-never-be-t
- https://openjdk.org/jeps/280
- https://johngrib.github.io/wiki/jvm-stack/