개발하던 중 갑자기 String의 + 연산이 어떻게 되는지 궁금해져 찾아봤다.
String + 연산 동작원리, StringBuilder, StringBuffer와의 성능 차이에 대해 알아보자.
String + 연산 동작원리
String의 +연산은 컴파일 타임에 new StringBuilder.append.toString으로 변환된다.
따라서 아래의 두 코드는 동일하다.
String s1 = "";
for(int i=1; i<=loop; i++) {
s1 += i;
}
String s2 = "";
for(int i=1; i<=loop; i++) {
s2 = new StringBuilder(s2).append(i).toString();
}
코드를 보면 문자열을 합치기 위해 매번 StringBuilder 인스턴스를 만들고, 다시 String으로 변환하는 과정을 거치기 때문에 비효율적이라는 것을 알 수 있다.
StringBuilder와 StringBuffer
문자열을 추가하는 방법엔 + 이외에 StringBuilder 이외에도 StringBuffer를 사용하는 방법이 있다.
둘의 차이는 synchronized 여부이다. append 이외에도 StringBuffer 클래스의 모든 함수에는 전부 synchronized가 붙어있다.
때문에 StringBuffer는 StringBuilder에 비해 성능이 조금 떨어지지만, 멀티쓰레드 환경에서 더 안정적이라는 특징이 있다.
// StringBuilder
public StringBuilder append(String str) {
super.append(str);
return this;
}
// StringBuffer
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
성능 측정
문자열을 합치는 방법들 간에 성능이 얼마나 차이날지 한번 측정해보자.
측정한 항목은 다음과 같다.
- String + 연산
- StringBuilder (반복마다 선언)
- StringBuilder (초기 1회 선언)
- StringBuffer
public class Main {
public static void main(String[] args) {
int loop = 200000;
// Operator +
String s1 = "";
long t1 = System.currentTimeMillis();
for(int i=1; i<=loop; i++) {
s1 += i;
}
System.out.println("operator +: " + (System.currentTimeMillis() - t1) + "ms");
// StringBuilder (반복마다 생성)
String s2 = "";
long t2 = System.currentTimeMillis();
for(int i=1; i<=loop; i++) {
s2 = new StringBuilder(s2).append(i).toString();
}
System.out.println("StringBuilder(new): " + (System.currentTimeMillis() - t2) + "ms");
// StringBuilder (초기 1회 생성)
StringBuilder stringBuilder = new StringBuilder();
long t3 = System.currentTimeMillis();
for(int i=1; i<=loop; i++) {
stringBuilder.append(i);
}
String s3 = stringBuilder.toString();
System.out.println("StringBuilder(once): " + (System.currentTimeMillis() - t3) + "ms");
// StringBuffer
StringBuffer stringBuffer = new StringBuffer();
long t4 = System.currentTimeMillis();
for(int i=1; i<=loop; i++) {
stringBuffer.append(i);
}
String s4 = stringBuffer.toString();
System.out.println("StringBuffer: " + (System.currentTimeMillis() - t4) + "ms");
}
}
측정 결과는 아래와 같다.
+ 연산과 매번 StringBuilder를 생성한 방식의 소모 시간은 거의 차이가 없고,
StringBuilder(최초 1회 선언)와 StringBuffer를 사용했을 때에 비해 훨씬 많은 시간이 걸리는 것을 알 수 있다.
매번 StringBuilder를 생성하고, 결과를 매번 변수에 대입해서 이런 차이가 나온것 같다.
operator +: 21884ms
StringBuilder(new): 21313ms
StringBuilder(once): 18ms
StringBuffer: 17ms
StringBuilder와 StringBuffer의 시간이 비슷해 둘만 따로 1000만번 돌려봤다.
StringBuffer가 약간 더 느린 것을 볼 수 있다.
StringBuilder(once): 379ms
StringBuffer: 497ms
문자열을 한두번 더하는거면 + 연산을 사용해도 큰 차이 없겠지만, 여러번 더해야 할 경우 StringBuilder나 멀티스레드 환경일 경우 StringBuffer를 미리 선언하여 사용하는게 좋을 것 같다.
[ 관련 글 ]
[Java] String Pool 저장 조건
Intro Java는 메모리 공간을 절약하기 위해 Heap 영역에 String Pool 영역을 만들고, 이 영역에 Constant 문자열을 저장한다. 그런데 이 String Pool 영역에 들어가는 조건이 애매하-다. 예를 들어, 아래의 s2, s
devjaewoo.tistory.com
참고자료
'Study > Java' 카테고리의 다른 글
[Java] String Pool 저장 조건 (0) | 2023.02.08 |
---|