Java에서 String 클래스는 불변성을 갖는다. 그래서 변하지 않는 문자열을 자주 사용하는 경우엔 좋은 성능을 기대할 수 없다.하지만 문자열에 대한 변경이 자주 일어나는 프로그램에서 String만 사용하게 된다면 효율적인 성능을 기대하기 어렵습니다. 그래서 우리는 StringBuilder , StringBuffer을 사용하여 효율적인 성능을 보일 수 있다.언제 StringBuilder를 사용하면 좋을지 또 언제 StringBuffer을 사용하면 좋을지에 대해 살펴보자
Java에서 String 객체는 한번 값이 할당되면 그 공간은 변하지 않는다. 하지만 Stringbuilder나 StringBuffer 객체는 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변하는 특성을 갖고 있습니다.
여기서 할당된 공간이 변하지 않는 특성을 불변(Immutable)성이라고 하고,
할당된 공간이 변하는 특성을 가변(mutable)성이라고 합니다.
그러면 다음과 같이 String과 StringBuilder, StringBuffer의 특성의 차이를 정리할 수 있습니다
즉, String 은 고정길이, StringBuilder, StringBuffer는 가변길이이다.
예시
String str = "strA";
StringBuilder sbd = new StringBuilder();
StringBuffer sbf = new StringBuffer();
sbd.append("sbdA");
sbf.append("sbfA");
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sbd.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());
str += "strB";
sbd.append("sbdB");
sbf.append("sbfB");
System.out.println("=============================");
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sbd.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());
String, StringBuilder, StringBuffer 타입의 변수를 선언하고 문자열을 수정하기 전에 객체의 주소를 해싱하여 값을 반환해주는 hashCode()의 반환 값을 출력하고, 문자열을 수정한 뒤 hashCode()의 반환값을 출력한다.
String 객체의 주소 : 3541040
StringBuilder 객체의 주소 : 1468177767
StringBuffer 객체의 주소 : 434091818
=============================
String 객체의 주소 : 1758230625
StringBuilder 객체의 주소 : 1468177767
StringBuffer 객체의 주소 : 434091818
다음과 같이 String 의 객체의 주소만 바뀐것을 확인할 수 있다.
더 자세히 알아보자.
String에 값을 할당하는 방법은 2가지다.
두가지 방식을 사용하여 예시코드를 작성해보자.
public class StringTest {
public static void main(String[] args) {
String data1 = "hello";
String data2 = new String("hello");
String data3 = "hello";
String data4 = new String("hello");
System.out.println(data1==data2);
System.out.println(data1==data3);
System.out.println(data1==data4);
System.out.println(data3==data4);
}
}
4개의 변수 모두 “hello”라는 문자열을 갖는데 결과가 상이하다.
그 이유는 String 타입 값 할당 방식에 따라 저장 방식이 다르기 때문이다.
리터럴 값으로 값을 할당하는 경우
String 을 리터럴 값으로 할당하는 경우엔 Heap 메모리 영역안의 특별한 메모리 공간인 String constant pool 에 저장된다. 만약 String constant pool에 존재하는 리터럴 값을 사용하게 된다면 새롭게 리터럴 값을 만들어 String constant pool에 저장하는 것이 아닌, 현재 존재하는 값을 사용하게 됩니다.

위와 같은 이유로 System.out.println(data1==data3); 의 실행 결과가 true 가 나오게 되는 것입니다.
new 키워드로 값을 할당하는 경우
new 키워드를 통해 String 변수에 값을 할당하게 되면 일반적인 객체와 동일하게 Heap 영역에 동적으로 메모리 공간이 할당되게 된다.
마찬가지로 같은 문자열이더라도 new 키워드를 한번 더 사용하게 되면 같은 값이지만 다른 메모리 공간(Heap 영역 안)을 참조하게 된다.

위와 같은 이유로 System.out.println(data2==data4); 에서 false 를 출력하게 되는 것입니다.
또한 System.out.println(data1==data2); 에서도 false 를 출력하는 것도 서로 상이한 메모리 공간을 참조하기 때문입니다.
String 과 달리 StringBuilder와 StringBuffer는 둘 다 크기가 유연하게 변하는 가변성을 갖는다고 우리는 배웠습니다.


위에 보이는 것 처럼 두 클래스 모두 AbstractStringBuilder 라는 추상 클래스를 상속받아 구현되어 있기 때문인데요.
AbstractStringBuilder 추상클래스의 멤버 변수엔 다음 2가지 변수가 존재합니다.
The value is used for character storage.The count is the number of characters used.StringBuilder와 StringBuffer 클래스의 문자열을 수정하고 싶으면 append() 메서드를 사용하게 되는데요.
append() 메서드는 AbstractStringBuilder 에 다음과 같이 구현되어 있습니다.
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
다음과 같이 StringBuilder , StringBuffer에 문자열을 추가하게 되면 추가할 문자열의 크기(길이)만큼 현재의 문자열을 저장하는 배열의 공간을 늘려주고, 늘려준 공간에 추가할 문자열을 넣어주는 방식으로 되어있습니다.
위에서 살펴본 내부동작을 통해 값이 변경되더라도 같은 주소공간을 참조하게 되는 것이며, 값이 변경되는 가변성을 띄게 되는 것입니다.
하지만 두 클래스의 기능은 동일하지만 한 가지 차이점이 존재합니다. 바로 동기화(Synchronization)에서의 차이점인데요.
StringBuilder는 동기화를 지원하지 않는 반면, StringBuffer는 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작할 수 있습니다.
그 이유는 StringBuffer는 메서드에서 synchronized 키워드를 사용하기 때문인데요.
java에서 synchronized 키워드는 여러개의 스레드가 한 개의 자원에 접근할려고 할 때, 현재 데이터를 사용하고 있는 스레드를 제외하고 나머지 스레드들이 데이터에 접근할 수 없도록 막는 역할을 수행합니다.
예를 들어 멀티스레드 환경에서 A 스레드와 B스레드 모두 같은 StringBuffer 클래스 객체 sb의 append() 메서드를 사용하려고 하면, 다음과 같은 절차를 수행하게 된다.
StringBuilder 클래스 주석에서 동기화가 필요할 경우 StringBuffer을 추천한다는 문구를 확인할 수 있다.

위에서 우리는 "String 은 불변성을 갖는다" 라는 것을 배웠습니다.
그렇기 때문에 우리는 변하지 않는 문자열을 자주 사용할 경우 String 타입을 사용하는 것이 성능면에서 유리할 것 입니다.
StringBuilder는 동기화를 지원하지 않는 반면, 속도면에선 StringBuffer 보다 성능이 좋습니다.
그렇기 때문에 우리는 단일 스레드 환경 과 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuilder를 사용하는 것이 성능면에서 유리할 것입니다.
StringBuffer는 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작할 수 있습니다.
그렇기 때문에 우리는 멀티 스레드 환경 과 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuffer를 사용하는 것이 성능면에서 유리할 것입니다.