String vs StringBuilder vs StringBuffer
- 프로그래밍 언어/자바
- 2025. 2. 10. 09:37
자바에는 String이라는 문자열이 있습니다. String은 객체로 기본적으로 힙메모리에 저장이 되어집니다.
그니까 String을 생성하게 되면 힙메모리에 저장이 되어집니다. 보통 객체를 만들때 new 객체();이렇게 만듭니다. 하지만 String은 이렇게 만들지 않습니다. String같은 경우는 String a = "리터럴"; 이렇게 만듭니다. String뿐만 아니라 기본형 변수(int,long...)을 가진 모든 참조형은 위 방식으로 생성 할 수 있습니다.
이를 우리는 초기화라고 부를 수 있습니다. 그니까 자바에는 String객체를 초기화하는 방법이 2가지가 존재합니다.
- new String()으로 초기화
- String a = "이런걸 리터럴 이라고 부릅니다.";
그렇다면 어떤 차이가 있을까요? 위에서 객체는 힙메모리에 저장이 된다고 했습니다. 애초에 2개 모두 힙메모리에 저장이 되는것은 맞습니다. 그렇다면 굳이 왜 초기화 방법이 2개일까요? 일반적인 객체방식으로 초기화하게 되면 그 값이 주소값으로 저장이 되어집니다. 결국 같은 값이어도 주소값이 서로 다르기 때문에 객체를 2번 초기화하게 되면 힙메모리에 2회 저장이 되어집니다.
이 방식은 굉장히 비효율적입니다. 왜냐하면 값이 같은데 할때마다 힙메모리에 저장이 되어지다니... 그렇다면 다른 초기화 방식인 String a; 방식(리터럴 방식)으로 초기화를 해야 하는 걸까요? 네 바로 리터럴 초기화 방식을 이용을 해야 합니다. 아까 객체는 힙메모리 안에 저장된다고 했었는데 이것도 힙메모리에 저장이 되긴합니다. 하지만 이 변수는 String pool에 저장이 되어집니다. String pool의 가장 큰 특징으로는 같은 값이 저장이 되어지면 객체를 저장하지 않습니다. 그러니까 같은 값으로 초기화를 많이 시켜도 부하가 없다는 뜻이 되어집니다.
고로 String a 리터럴 방식으로 사용하는것이 좋습니다. 근데 의문이 생겼습니다. 리터럴 연산은 기본적으로 + 으로 하게 되어지는데 String 애초에 불변객체입니다. 불변객체로 만든 이유는 쓰레드 세이프하게 만들기 위함입니다. (다른 이유도 있지만 다른건 찾아보면 좋을거 같습니다.) 그러니까 멀티 쓰레드 환경에서 그 값을 공유했을때 값이 변경이 되지 않는다는것을 전재로 하고 있습니다. 불변객체로 만들게 되면 수정이 불가능해집니다. 근데 String은 수정이 되어지는거 같습니다. 이상합니다. 불변객체인데 수정이 되어지다니.. 이건 사실 그렇게 보일뿐이지 실제로는 그렇지 않습니다. 바로 수정 혹은 + 연산을 할때마다 힙메모리에 저장이 되어진다는것인데요.
예를들어 String a = "a"; a += "b"; ..+="c";
이렇게 저장한다고 가정했을떄 힙모메리에는 a,ab,abc가 저장이 되어집니다. 이제 a,ab는 필요없는데 말이죠 물론 지금 같은 경우는 짧기 때문에 gc가 알아서 없애줄겁니다. 하지만 길다면 어떨까요? 성능에 문제가 발생할 수 있습니다. 그렇다면 어떻게 해야 할까요? 자바에는 두 가지 객체를 제공합니다. StringBuilder와 StringBuffer입니다. 두 객체는 하는일은 같습니다. 그렇다면 무엇이 다르길래 이렇게 구분을 한것일까요?
일단 이건 조금 이따 말씀드리고 일단 두 객체를 String객체에서 +연산할때 언급한 이유에 대해서 설명 드리겠습니다. 두 객체의 공통점은 이 객체들은 불변 객체가 아니라 가변객체입니다. 그러니까 수정이 용이하다는 뜻이 됩니다. 이들을 활용하면 + 연산을 할때마다 객체를 힙메모리에 저장할 필요가 없을거 같습니다. 결국 최소 한번까지 객체 저장 횟수를 줄일 수 있으니 성능을 정리를 하면 +연산이 많이 일어나는 경우라면 StringBuilder, StringBuffer을 사용하시면 됩니다.
근데 아까 불변 객체를 만든 이유 (다른 이유도 있지만)가 쓰레드 세이프하기 때문이라고 했습니다. 그러면 두 객체는 가변 객체이니 쓰레드 세이프 하지 않는 걸까요? 다행스럽게도 둘중 하나만 쓰레드 세이프 하지 않습니다. 바로 StringBuilder입니다. StringBuffer같은 경우는 쓰레드 세이프 합니다.
그러니까 정리하면
- 초기화는 리터럴 방식을 사용할것
- +연산이 자주 발생하는 경우 StringBuilder,StringBuffer을 사용할것
- 멀티 쓰레드 환경에서 사용하는 경우 StringBuffer을 사용
이렇게 정리할 수 있을거 같습니다.