문자열 연결은 느리니 주의하라
문자열을 연결할 때 단순히 ‘+’를 사용해 아주 쉽게 여러 문자열을 하나로 합칠 수 있다.
하지만 한 줄 짜리 출력값 혹은 작고 크기가 고정된 객체의 문자열 표현을 만드는 정도라면 괜찮지만, 본격적으로 사용하게 된다면 성능 저하를 감내하기 어렵다.
문자열 연결 연산자로 문자열 n개를 잇는 시간은 n^2에 비례한다. 문자열은 불변이기 때문에 두 문자열을 연결할 경우 양쪽의 내용을 모두 복사해야해 성능 저하가 필연적이다.
문자열 연결 연산자 성능
1
2
3
4
5
6
7
public String statement() {
String result = "";
for (int i = 0; i < 100_000; i++) {
result += "abc"
}
return result;
}
- 함수가 실행되는데 3.43초가 걸렸다. 수가 더 커진다면 심각하게 느려질 것이다.
그렇다면 StringBuilder를 사용하면 어떻게 될까?
1
2
3
4
5
6
7
public String statement() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < 100_000; i++) {
b.append("abc");
}
return b.toString;
}
- 1초도 걸리지 않는다.
- Java 6 이후 문자열 연결에 대한 성능을 다방면으로 개선했지만, 두 메서드의 성능 차이는 보다시피 매우 크다.
- StringBuilder는 String과 다르게 가변이다. 이 부분에서 내부 동작의 차이가 발생해 성능 차이가 발생한다.
- String
- 현재 문자열과 새로 추가할 문자열의 길이를 더한 새로운 배열을 생성한다.
- 현재 String 객체의 내용을 배열에 복사하고, 추가할 문자열 또한 배열에 복사한다.
- 새로운 배열을 기반으로 새로운 String 객체를 생성한다.
- 이 과정을 거치면서 매우 많은 객체가 생성되게 된다.
- StringBuilder
- 초기 용량을 가진 배열을 생성한다.
- 문자열을 추가할 때 배열의 남은 공간을 확인하고 남은 공간이 충분하면 추가할 문자열을 배열에 복사한다.
- 남은 공간이 부족하다면 현재 배열의 크기를 늘리고 기존 배열의 내용을 새로운 배열에 복사하고, 추가할 문자열을 복사한다.
- 위와 같은 과정으로 비교적 객체의 생성과 메모리 할당이 적게 발생해 성능 차이가 크게 나는 것이다.
정리
성능에 신경써야 하는 경우, 많은 문자열을 연결할 때 문자열 연결 연산자를 사용하지 말고 StringBuilder를 사용하자.
String은 불변이라는 사실을 잊으면 안된다.
문자 배열을 사용하거나, 문자열을 연결하지 않고 하나씩 처리하는 방법도 있다.