1. Introduction
String의 + 연산자가 느리다는 것은 널리 알려진 사실이다.
String은 내부적으로 char[]을 사용하는데 이 배열은 변경이 불가능하다.
String은 인스턴스 생성 시에만 char[]에 값을 넣을 수 있어서 아래와 같은 코드에서 String의 new 연산이 O(n)번 실행된다.
public String repeatString(String target, int n)
{
String result = "";
for(int i=0; i < n; i++ )
result += target;
return result;
}
반면, 아래 코드처럼 StringBuffer를 사용하도록 바꾸면 new 연산자는 한번만 실행된다.
public static StringBuffer repeatString(String target, int n)
{
StringBuffer result = new String Buffer();
for(int i=0; i<n; i++)
result.append(target);
return result;
}
StringBuffer의 경우 String과 마찬가지로 내부적으로 char[]을 사용하지만 이 값은 변경이 가능하다.
StringBuilder는 StringBuffer와 매우 비슷하다. 소스 코드에서 Ctrl+F 후 ReplaceAll을 해도 에러가 생기지 않는다.
(물론 happen to be 에러가 생길 수도 있다 ^^;; 두 클래스의 모든 메서드가 이름과 내용이 똑같다는 의미다)
두 클래스의 차이점은 StringBuilder는 동기화를 지원하지 않아 single thread에서만 사용 가능, StringBuffer는 동기화를 지원하기 때문에 multi-thread에서도 사용 가능하다는 점이다.
따라서 multi thread상황에서 문자열이 리소스로 사용된다면 선택의 여지 없이 StringBuffer를 사용, single thread 상황이라면 동기화를 고려하지 않는 StringBuilder를 이용해 더 빠른 performance를 취하는게 좋다고 알고 있다.
그 차이가 어느 정도 되는지 실험을 통해 비교해보도록 하자.
2. Source Code
실험 결과에 앞서, 먼저 다음과 같은 더러운 소스코드를 만들었다.
문자열 "a"를 반복해서 concaternation하는 간단한 프로그램이다.
실험변수1: String, StringBuffer, StringBuilder
실험변수2: concaternation 횟수 (String의 경우엔 +연산자, StringBuilder나 StringBuffer의 경우엔 append 메서드를 사용)
public class StringPerformance {
public static double mkLongStr(int len)
{
long start = System.nanoTime();
String str = "";
for(int i=0 ; i < len ; i++) str += "a";
long end = System.nanoTime();
return (end-start)/(double)1_000_000_000;
}
public static double mkLongStrBuffer(int len)
{
long start = System.nanoTime();
StringBuffer sb = new StringBuffer();
for(int i=0 ; i < len ; i++) sb.append("a");
long end = System.nanoTime();
return (end-start)/(double)1_000_000_000;
}
public static double mkLongStrBuilder(int len)
{
long start = System.nanoTime();
StringBuilder sb = new StringBuilder();
for(int i=0 ; i < len ; i++) sb.append("a");
long end = System.nanoTime();
return (end-start)/(double)1_000_000_000;
}
public static void main(String[] args) {
int[] lenList = new int[]{10_000, 20_000, 40_000, 80_000, 160_000, 320_000, 640_000,
1_280_000, 2_560_000, 5_120_000, 10_240_000, 20_480_000, 40_960_000, 81_920_000, 163_840_000};
for(int len : lenList)
{
// System.out.println("String\t" + len + "\t" + mkLongStr(len));
System.out.println("StringBuffer\t" + len + "\t" + mkLongStrBuffer(len));
System.out.println("StringBuilder\t" + len + "\t" + mkLongStrBuilder(len));
}
}
}
3. Result
x축: String의 경우 + 연산자의 사용 횟수, StringBuffer와 StringBuilder의 경우 append의 사용 횟수
y축: 시간 (첫번째 그래프는 초(second), 두번째 그래프는 그 값(second)에 log2를 취한 값)
String은 16만번을 넘어서니 너무 느려서 사용할 수가 없었다.
StringBuffer나 StringBuilder는 굉장히 좋은 성능을 보여준다.
String은 x축 값이 2배 늘어날 때 y축 값이 2.5~5배 정도 늘어난다. (consistent하지 않음)
StringBuffer, StringBuilder는 x축 값이 2배 늘어날 때 y축 값이 2배 정도 늘어난다.
StringBuilder가 StringBuffer가 2배 정도 더 빠르다
4. Conclusion
String의 + 연산자는 횟수가 많아지면 사용이 불가능할 정도로 성능이 좋지 않다.
문자열 가지고 할 일이 많다면 반드시 StringBuffer나 StringBuilder를 사용하도록 하자.
StringBuffer가 StringBuilder보다 2배 정도 느리다고 해도 append를 163,840,000번 (약 1억7천번..) 하는데 2.65초밖에 걸리지 않는다. single thread의 웬만한 상황에선 둘 중 아무거나 써도 될 듯 한데 뭐…사용하는 사람 마음이겠다
'IT개발 > Java' 카테고리의 다른 글
J2EE란? (0) | 2019.12.04 |
---|---|
[JAVA] flush() (2) | 2014.10.13 |
[Java] java compile 시 utf-8 문제 (癤?) : Unexpected character (癤) at position 0. (0) | 2014.04.24 |
[Java] trim(), charAt() 함수에 대해 알아보자 (0) | 2014.04.06 |
[Java] StringBuffer 클래스에 대해서 앞아보 (0) | 2014.04.02 |