면접을 보면 면접관들은 지원자의 긴장을 덜어주기 위해 아주 간단한 질문을 하는 경우가 있다.
그 질문중에 대표적인 것으로 String, StringBuilder, StringBuffer의 차이가 있다.
당연하게 알고 있는 지식들이라 생각해서, 면접자가 모를거라는 대비를 안한 면접관들은 함께 당황할 수 있다.(면접관을 당황시킬 수 있는 절호의 기회!)
이 질문에 대해 궁금해서 찾아보고 가변객체와 불변객체의 차이, 동시성이 고려된 객체인지 아닌지를 외우고 있을 수 있다.
일단 기본적인 사용법은 다음과 같다.
String : 문자열 연산이 적고 멀티쓰레드 환경에서 사용
StringBuffer : 문자열 연산이 많고, 멀티쓰레드 환경일 경우
StringBuilder : 문자열 연산이 많고, 단일쓰레드 환경이거나 동기화를 고려할 필요가 없는 경우
위 설명을 보면 머릿속으로 가설을 만들 수 있다.
: 단일쓰레드 환경에서 실험할 때 StringBuilder > StringBuffer > String 순으로 성능이 좋을 것
이 과정을 실제 코드로 실험해보고 어떤 차이가 있나 확인해보고 정확하게 알아보자!
세 가지 종류의 객체를 생성하여 반복적으로 객체에 값을 추가하며 연산처리속도와 쌓이는 메모리를 값을 출력하도록 실험해보았다.
1. String
String str = "";
while (i>0){
str = str + "world";
i++;
}
String
100,000번 반복 변경
HeapMemory -> 721420288
실행 시간 : 4.393sec
(다음 실험 무의미)
2.StringBuilder
StringBuilder str = new StringBuilder();
while (i > 0) {
str = str.append("world");
i++;
}
StringBuffer
100,000번 반복 변경
HeapMemory -> 272629760
실행 시간 : 0.217sec
1,000,000번 반복 변경
HeapMemory -> 272629760
실행 시간 : 2.947sec
10,000,000번 반복 변경
HeapMemory -> 350224384
실행 시간 : 27.693sec
3.StringBuffer
StringBuffer str = new StringBuffer();
while (i > 0) {
str = str.append("world");
i++;
}
StringBuilder
100,000번 반복 변경
HeapMemory -> 272629760
실행 시간 : 0.219sec
1,000,000번 반복 변경
HeapMemory -> 272629760
실행 시간 : 2.586sec
10,000,000번 반복 변경
HeapMemory -> 350224384
실행 시간 : 27.811sec
결론:
String으로 생성된 객체에 문자열을 추가할 경우 새로운 객체로 힙메모리에 저장, 기존의 객체도 메모리에 남아 객체가 쌓이게 된다. (지속적으로 변경되는 상황에 비효율)
: String은 불변객체로 사용하기 위해 만들어진 클래스 -> 변경이 아닌 새로운 객체 생성
StringBuffer, StringBuilder는 실험결과에서 크게 차이가 없었음
클래스 내부를 확인해본 결과
: StringBuffer는 synchronized 키워드가 포함되어 동시성 문제를 막을 수 있는 가변객체.
StringBuilder의 경우 but with no guarantee of synchronization이라는 주석에서 알 수 있듯이 동시성을 고려하지 않은 가변객체임을 확인할 수 있었다.
추가적으로 생각했던 가설과는 다르게 StringBuffer와 StringBuilder의 성능차이는 거의 없었다.
다만 의도에 맞는 객체를 사용 -> 작성한 코드 의도를 명확하게 보여주는 것이 좋을 것이라는 생각을 하게 됐다.
이 글을 통해, 면접관을 잘못된 방향으로 당황시키는 불상사가 조금이나마 줄었으면 좋겠다.