Java9 부터는 String이 StringBuilder보다 빠를 수 있다
개요
- 작성 계기
- Java 9 이전 :
String
<StringBuilder
- Java 9 이후 :
String
>StringBuilder
- 진짜일까?
- 결론
0. 작성 계기
회사에서 작업 도중 위와 같은 코드 리뷰가 들어왔다.
개인적으로 나는 Java
랑 그렇게 친하다고 생각하진 않는다.
코딩 테스트 언어도 C++
로 선택했었고, 회사 외부에서 하는 프로젝트는 Kotlin
또는 Dart
를 사용한다.Java
는 현재 회사 업무랑 병행하면서 조금이라도 익숙해지고자 알고리즘 문제를 풀때 사용하는데, 애정이 없다보니 무언가 궁금한 키워드가 있을때는 수박 겉핥기 식으로 대충 이해하고 넘기는것 같다.
이 기회에 모르는 지식이 있으면 제대로 짚고 넘어가자는 생각이 들었다.
1. Java 9 이전 : String
< StringBuilder
Java
로 알고리즘 문제를 풀때 대부분의 사람들은 문자열 관련 로직, 특히 문자열의 변경이 필요되는 로직에서는 StringBuilder
를 사용한다.
예전에 정리했던 Github 자료 (Click) 에서도 String
은 문자열의 변경이 생길 경우, Heap
영역에 새로운 객체가 생성되어 재할당되는 것으로 언급했었다.
때문에 final
이 아닌 mutable(가변)
의 특징이 있는 StringBuilder
로 문자열 변수를 선언하는 것이 권장됐다.
여기서 Synchronized
가 적용되는, 이를테면 멀티 스레드 환경에서 Thread-safe
한 동작을 원할 경우 StringBuffer
를 사용하는 것이 권장됐던걸로 알고있다.
조금더 공부를 해보니, Java 9
이후 버전부터는 String
객체에 변경이 생겼다는 것을 알 수 있어다.
2. Java 9 이후 : String
> StringBuilder
String
클래스의 문자열 연결 연산이 StringBuilder
로 사용되었던 이전과는 달리, Java 9
부터는 StringConcatFactory
클래스의 makeConcatWithConstants
를 사용하는데, runtime 시점에 바이트 코드를 생성하기 때문에 StringBuilder
를 사용하는 것 보다 오버헤드가 적다고 한다.
This class provides two forms of linkage methods: a simple version (makeConcat(java.lang.invoke.MethodHandles.Lookup, String, MethodType)) using only the dynamic arguments, and an advanced version (makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup, String, MethodType, String, Object...) using the advanced forms of capturing the constant arguments. The advanced strategy can produce marginally better invocation bytecode, at the expense of exploding the number of shapes of string concatenation methods present at runtime, because those shapes would include constant static arguments as well.
- Reference 1 : https://www.baeldung.com/java-string-concatenation-invoke-dynamic
- Reference 2 : https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/StringConcatFactory.html
- Reference 3 : https://openjdk.org/jeps/280
3. 진짜일까?
글로만 보니 딱히 와닿지가 않았다.
그래서 아래와 같이 테스트 코드를 돌려봤다.
(1) 계속 해서 문자열을 더하기
@Test
void testStringPerf1() {
long start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder("test");
for (int i = 0; i < 10000; i++) {
builder.append(" StringBuilder");
}
long end = System.currentTimeMillis();
System.out.println("## 실행 시간 StringBuilder : " + (end - start));
String builder2 = "test";
long start2 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
builder2 += " StringBuilder";
}
long end2 = System.currentTimeMillis();
System.out.println("## 실행 시간 String : " + (end2 - start2));
}
## 실행 시간 StringBuilder : 0
## 실행 시간 String : 84
(2) 한 번만 문자열을 더하기
@Test
void testStringPerf2() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000_000; i++) {
StringBuilder builder = new StringBuilder("test");
builder.append(" StringBuilder");
}
long end = System.currentTimeMillis();
System.out.println("## 실행 시간 StringBuilder : " + (end - start));
long start2 = System.currentTimeMillis();
for (int i = 0; i < 100_000_000; i++) {
String builder2 = "test";
builder2 += " StringBuilder";
}
long end2 = System.currentTimeMillis();
System.out.println("## 실행 시간 String : " + (end2 - start2));
}
## 실행 시간 StringBuilder : 1090
## 실행 시간 String : 101
결론
- 문자열을 한 번만 더할때 :
String
>StringBuilder
- 문자열을 여러번 더할때 :
String
<StringBuilder