개요
Java에서 String 클래스는 불변성을 갖습니다. 그래서 변하지 않는 문자열을 자주 사용하는 경우 좋은 성능을 기대할 수 있는데요, 하지만 문자열에 대한 변경이 자주 일어나는 프로그램에서 String만 사용하게 된다면 효율적인 성능을 기대하기 어렵습니다. 그래서 StringBuilder, StringBuffer를 사용하여 효율적인 성능을 보일 수 있습니다. 언제 StringBuilder와 StringBuffer를 사용하면 좋을지 살펴보도록 하겠습니다.
String VS StringBuilder & StringBuffer
Java에서 String 객체는 한번 값이 할당되면 그 공간은 변하지 않습니다. 하지만 StringBuilder나 StringBuffer 객체는 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변하는 특성을 가지고 있는데요! 여기서 할당된 공간이 변하지 않는 특성을 불변(Immutable)성이라고 하고, 할당된 공간이 변하는 특성을 가변(mutable)성이라고 합니다.
그러면 String과 StringBuider, StringBuffer의 특성의 차이를 정리해보면 다음과 같습니다.
String | StringBuilder & StringBuffer |
불변성을 갖는다 (Immutable) | 가변성을 갖는다 (Mutable) |
이해를 위해 하나의 코드를 잠깐 살펴볼게요 !
String str = "str입니다.";
StringBuilder sb = new StringBuilder();
StringBuffer sf = new StringBuffer();
sbd.append("sb입니다.");
sbf.append("sf입니다.");
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sbd.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());
str += "str 안녕하세요 ?";
sbd.append("sb 안녕하세요 ?");
sbf.append("sf 안녕하세요 ?");
System.out.println("=============================");
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sbd.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());
String 객체의 주소 : 3541040
StringBuilder 객체의 주소 : 1468177767
StringBuffer 객체의 주소 : 434091818
=============================
String 객체의 주소 : 1758230625
StringBuilder 객체의 주소 : 1468177767
StringBuffer 객체의 주소 : 434091818
다음과 같이 String의 객체의 주소만 바뀐 것을 확인할 수 있습니다.
1) 과연 String은 어디에 있는 메모리를 참조하여 값을 가지고 있을까요
2) String 객체의 값이 변할 때마다 어떻게 동작할까요 ?
String Constant Pool
먼저, String 변수에 값을 할당하는 방법은 2가지가 존재합니다.
- 리터럴 변수를 대입하는 방법
- new 키워드를 사용하는 방법
두 가지의 방식을 사용해 예시 코드를 작성해보도록 하겠습니다.
String strA = "abc";
String strB = new String("abc");
String strC = "abc";
String strD = new String("abc");
System.out.println(strA==strB); //false
System.out.println(strA==strC); //true
System.out.println(strB==strD); //false
3개의 변수 모두 "abc"라는 문자열을 갖는데 3개의 주소비교(==)의 결과는 ?.... 모두 true가 아닌 false 값이 확인되는 것을 볼 수 있습니다.
그 이유는 String 타입 값 할당 방식에 따른 저장 방식이 다르기 때문이죠.
[ 리터럴 값으로 값을 할당하는 경우 ]
String을 리터럴 값으로 할당하게 되면, Heap 메모리 영역 안의 특별한 메모리 공간인 String Constant Pool 에 저장됩니다.
그러니까, 결국 이미 String Constant Pool 안에 리터럴 값이 존재하면 새롭게 리터럴 값을 생성하고 저장하지 않고 존재하는 값을 사용하게 됩니다.
이러한 이유로 리터럴 변수를 통해 생성한 strA와 strC의 참조를 비교하는 구문은 True 값이 나오게 되는 것이죠 !
[ new 키워드로 값을 할당하는 경우 ]
이번엔 new 키워드를 통해 String 변수에 값을 할당하는 과정을 살펴볼게요.
new 키워드를 통해 String 변수에 값을 할당하게 되면 일반적인 객체와 동일하게 Heap 영역에 동적으로 메모리 공간이 할당되게 됩니다.
마찬가지로 같은 문자열이더라도 new 키워드를 한번 더 사용하면 같은 값이더라도 다른 메모리 공간(Heap 영역 안)을 참조하게 되죠.
이러한 이유로 strD와 strC는 각각 참조하는 부분이 다르기 때문에 False 값을 확인할 수 있게 되는 것입니다. (strB도 마찬가지)
StringBuilder VS StringBuffer
String과 달리 StringBuilder와 StringBuffer는 가변성을 갖는다고 배웠습니다.
StringBuilder, StringBuffer에 문자열을 추가하게 되면 추가할 문자열의 크기(길이)만큼 현재의 문자열을 저장하는 배열의 공간을 늘려주고, 늘려준 공간에 추가할 문자열을 넣어주는 방식으로 동작합니다.
String은 ? 문자열을 추가할 때마다 해당 문자가 String Constant Pool에 저장됩니다.
이에 따라 값이 변경되더라도 같은 주소공간을 참조하게 되는 것이며, 값이 변경되는 가변성을 띄게 되는 것이죠.
그렇다면, 대체 StringBuilder와 StringBuffer의 차이는 무엇일까요 ? 바로 동기화(Synchronization)에서의 차이가 있습니다.
- StringBuilder는 동기화를 지원하지 않습니다.
- StringBuffer는 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작합니다.
위와 같은 이유는 StringBuffer는 메소드에서 synchronized 키워드를 사용하기 때문이에요. Java에서 synchronized 키워드는 여러개의 스레드가 한 개의 자원에 접근하려고 할 때, 현재 데이터를 사용하고 있는 스레드를 제외하고 나머지 스레드들이 데이터에 접근할 수 없도록 막는 역할을 수행합니다.
예를 들어서 멀티스레드 환경에서 A스레드와 B스레드 모두 같은 StringBuffer 클래스 객체 sb에 접근해 append() 메소드를 사용하려고 한다면 ?
- A스레드 : sb의 append() 동기화 블록에 접근 및 실행
- B스레드 : A스레드 sb의 append() 동기화 블록에 들어가지 못하고 block 상태가 된다.
- A스레드 : sb의 append() 동기화 블록에서 탈출
- B스레드 : block에서 running 상태가 되며 sb의 append() 동기화 블록에 접근 및 실행
따라서 결론적으로 단일스레드 환경이 아닌 멀티스레드 환경에서 동기화가 필요한 경우 StringBuffer를 사용하도록 합시다!
정리
- String은 변하지 않는 문자열을 자주 사용할 때 사용하자.
- StringBuilder는 단일스레드 환경, 문자열 추가, 수정, 삭제등이 빈번히 발생하는 경우 사용하자.
- StringBuffer는 멀티스레드 환경, 문자열 추가, 수정, 삭제등이 빈번히 발생하는 경우 사용하자.
< 참고 자료 >
StringBuilder와 StringBuffer는 무슨 차이가 있는가?
Java에서 String 클래스는 불변성을 갖습니다. 그래서 변하지 않는 문자열을 자주 사용하는 경우엔 좋은 성능을 기대할 수 있습니다. 하지만 문자열에 대한 변경이 자주 일어나는 프로그램에서 Stri
velog.io