String 객체 연산시 StringBuffer를 사용해야하는 이유

자바 프로그래밍을 처음 배우던 시기에 문자열을 반복적으로 연산하는 방법으로 String 객체를 사용하곤 했다. 사실 StringBuffer를 알게 된 바로 지금까지도…

별을 1만개 찍는 코드로 String 객체를 사용하는것과 StringBuffer를 사용하는 것의 차이를 비교해보겠다.

1
2
3
4
5
6
7
8
9
10
public class shinningStars {
public static void main(String[] args){
String str = "";

for(int i = 0; i<10000; i++){
str = str+"*";
}
System.out.println(str);
}
}

이렇게만 코드를 짜면 별을 1만개 찍을수는 있지만, 성능을 체크할 수 없다. 따라서 System 클래스의 currentTimeMillis()를 이용해서 성능을 체크해보겠다. System.currentTimeMillis()는 해당메서드가 호출되는 시점의 시스템 시간을 불러오는 메서드이다. 프로그램이 시작되기 전 시간과 프로그램의 종류 후 시간을 연산한다면 프로그램의 실행시간을 알 수 있을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class shinningStars {
public static void main(String[] args){

long startTime = System.currentTimeMillis();

String str = "";
for(int i = 0; i<10000; i++){
str = str+"*";
}
System.out.println(str);

long endTime = System.currentTimeMillis();
System.out.println("실행시간 : "+(endTime-startTime));
}
}

실행할때마다 실행시간이 조금씩 다르지만 대체적으로 46초~55초가 출력되고 있다.

그럼 이제 이 코드에서 문자열 변수를 StringBuffer로 타입을 바꿔서 다시 체크해보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class shinningStars {
public static void main(String[] args){

long startTime = System.currentTimeMillis();

StringBuffer str = new StringBuffer();
for(int i = 0; i<10000; i++){
str.append("*");
}
System.out.println(str);

long endTime = System.currentTimeMillis();
System.out.println("실행시간 : "+(endTime-startTime));
}
}

이번엔 실행시간이 1-2초로 대폭 줄어든걸 확인할 수 있다. 같은 내용을 출력하면서도 속도가 10배이상 향상된 것이다.

이렇게 차이가 나는 이유는 String 타입으로 문자열 변수를 연산할 경우, for문안에서 계속 새로운 String 데이터를 생산한다. 반면 StringBuffer를 사용하면, 새로운 객체 생성없이 기존의 객체에 값만 append하기 때문이다.

Java API 문서를 보면, StringBuffer에 대한 설명으로 StringBuffer는 가변적인(immutable) 문자형들의 순서집합(sequence)이라고 정의했다. StringBuffer는 String과 같지만, 수정할 수 있다는 특징도 언급했다.

A thread-safe, mutable sequence of characters. A string buffer is like a String, but can be modified. At any point in time it contains some particular sequence of characters, but the length and content of the sequence can be changed through certain method calls.

StringBuffer는 capacity가 존재하는데, 모든 StringBuffer는 이 capacity를 초과하지 않는 문자형 시퀀스를 가지고 있다고 한다. 여기까지 읽으면 배열의 속성처럼(?) 들리는데, 배열과 달리 StringBuffer는 internal bufffer array를 초과할 경우 자동으로 capacity를 늘린다고 한다. 이게 append()를 이용해 값을 늘려나가는 immutable한 특징을 설명하는 것 같다.

Every string buffer has a capacity. As long as the length of the character sequence contained in the string buffer does not exceed the capacity, it is not necessary to allocate a new internal buffer array. If the internal buffer overflows, it is automatically made larger.

그러나 String 타입에 비하면 StringBuffer는 무거운 타입의 객체이므로 immutable하게 사용할 목적이 아니라면, string을 사용하는게 전체적인 프로그램 성능에 더 도움이 될 것 같다.