살펴보고 돌아서서 5분 후면 까먹는 String, StringBuffer, StringBuilder에 대한 정리.
String
String
은 Immutable Class (불변 클래스)
이다. 불변 클래스라 함은 아래 코드와 같이 str1에 str2를 더했을 때 원래의 객체가 변하는 것이 아니라 새로운 객체가 생성되는 것을 의미한다.
코드 1.
1 2 3 4 5 6 7 8
| String str1 = "Lazy"; String str2 = "Rodi"; System.out.println(" str1 => " + str1 + ": " + str1.hashCode()); System.out.println(" str2 => " + str2 + ": " + str2.hashCode()); str1 += str2; System.out.println("str1 += str2; => " + str1 + ": " + str1.hashCode());
|
결과값 1. 주소가 바뀐 것을 확인할 수 있다.
1 2 3
| str1 => Lazy: 2361236 str2 => Rodriguez: 2552738 str1 += str2; => LazyRodriguez: -1189801674
|
위의 예제에서 처음 str1
이 차지하고 있던 공간이 낭비된다는 것을 알 수 있다.
이 낭비를 막기 위해 StringBuffer
를 살펴보자.
StringBuffer
StringBuffer
는 Immutable Class
가 아니며, append()
메소드를 제공하여 문자열 연산을 할 수 있게 도와준다.
코드 2.
1 2 3 4 5 6 7 8 9
| StringBuffer strBuf = new StringBuffer(); String str1 = "Lazy"; String str2 = "Rodriguez"; System.out.println("strBuf => " + strBuf + " : " + strBuf.hashCode()); strBuf.append(str1); System.out.println("strBuf => " + strBuf + ": " + strBuf.hashCode()); strBuf.append(str2); System.out.println("strBuf => " + strBuf + ": " + strBuf.hashCode());
|
결과값 2. 새로 생성되는 객체가 없다.
1 2 3
| strBuf => : 366712642 strBuf => Lazy: 366712642 strBuf => LazyRodriguez: 366712642
|
별 차이 없는 것으로 볼 수도 있고, StringBuffer
를 생성할 때의 자원이 걱정될 수도 있겠지만, 문자의 +
연산이 빈번하게 이루어진다면 메모리의 낭비가 커질 수 밖에 없고 이 때 StringBuffer
를 String
보다 효율적으로 사용할 수 있게 된다.
String
과 StringBuffer
모두 char[]
이다. String
은 +
연산 시 새로운 배열을 만들어내고 StringBuffer
은 미리 배열을 여유있게 잡아둔다.
이런 이유때문에 StringBuffer
의 배열이 꽉 차서 메모리를 더 잡아주는 지점에서 성능 저하가 살짝 나타난다.
보통 StringBuffer
가 String
보다 성능이 뛰어나다. String
의 경우에도 Compiler가 +
연산을 StringBuffer
로 자동 변환해주긴 하지만 모든 +
연산을 커버하지는 않는다고 한다.
StringBuilder
위에서 살펴본 StringBuffer
는 멀티 스레딩 환경에서 Thread-safe를 위해 동기화(Synchronized)
가 이루어진다.
StringBuilder
는 StringBuffer
에서 동기화 기능을 제거한 것이다. 고로, 멀티 스레딩 환경이 아니라면 StrigBuilder
를 쓰는 것이 낫다.
성능시험
간단한 코드로 속도를 비교해 보도록 하자. loop 단위가 작을 때에는 currentTimeMillis()
대신 nanoTime()
을 사용하면 된다.
(사실 아래 코드가 측정에 적당한 코드인지 불안하긴 하다…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| String str1 = "Lazy"; String str2 = "Rodriguez"; StringBuffer strBuffer = new StringBuffer(); StringBuilder strBuilder = new StringBuilder(); int i = 0; long max = 50000; long s, e; s = System.currentTimeMillis(); System.out.println("'StringBuffer' start = " + s); for (i = 0; i < max; i++) { strBuffer.append(str2); } e = System.currentTimeMillis(); System.out.println("'StringBuffer' start = " + e); System.out.println("'StringBuffer' spend time = " + (e - s)); s = System.currentTimeMillis(); System.out.println("'StringBuilder' start = " + s); for (i = 0; i < max; i++) { strBuilder.append(str2); } e = System.currentTimeMillis(); System.out.println("'StringBuilder' start = " + e); System.out.println("'StringBuilder' spend time = " + (e - s)); s = System.currentTimeMillis(); System.out.println("'+' start = " + s); for (i = 0; i < max; i++) { str1 += str2; } e = System.currentTimeMillis(); System.out.println("'+' start = " + e); System.out.println("'+' spend time = " + (e - s));
|
아래 결과는 3회 측정 후 평균 낸 값이다.
기초적인 환경에서는 StringBuilder
성능이 제일 좋다는 것을 알 수 있다.
종류 \ 수행 | 100회 (ns) | 1000회 (ns) | 10000회 (ms) | 20000회 (ms) | 30000회 (ms) | 50000회 (ms) |
---|
String | 274526 | 9077829 | 539 | 1910 | 4208.667 | 10992.67 |
StringBuffer | 305514.7 | 583808.3 | 2 | 3.666667 | 6 | 6.333333 |
StringBuilder | 117393.7 | 202339.7 | 2.333333 | 2.333333 | 3.333333 | 7 |
내 맘대로 정리
- String
- 단순한 코드, 문자열의 불변성이 유지되는 코드에서 사용
- StringBuffer
- StringBuilder
- Single thread 환경에서 문자열 결합에서는 가장 좋은 성능을 보임
출처
아래의 글들을 교재삼아 작성하였습니다.