String 변수에 값을 할당하는 방법은 2가지가 있습니다. 리터럴 변수를 대입하는 방법과 new 키워드를 사용하는 방법 입니다. 위 두가지 방식을 사용하여 예시코드를 작성해보겠습니다.
String str1 = "test";
String str2 = "test";
String str3 = new String("test");
String str4 = new String("test");
System.out.println(str1==str2); //true
System.out.println(str3==str4); //false
System.out.println(str1==str3); //false
3개의 변수 모두 "test"라는 문자열을 갖는데 3개의 주소비교(==)의 결과가 다릅니다. 그 이유는 String 타입 값 할당 방식에 따른 저장 방식이 다르기 때문입니다. String 을 리터럴로 선언할 경우엔 Heap 메모리 영역안의 또 다른 메모리 공간인 String constant pool에 저장됩니다.
위 그림과 같이 String constant pool에 존재하는 리터럴 값을 사용하면 새롭게 리터럴 값을 만들어 String constant pool에 저장하는 것이 아닌, String의 intern() 메서드가 호출되게 되고 intern() 메서드는 주어진 문자열이 string constant pool에 존재하는지 검색하고 있다면 그 주소값을 반환하고 없다면 string constant pool에 넣고 새로운 주소값을 반환합니다. 그래서 str1==str2의 실행 결과는 true가 나오게 됩니다.
new 키워드를 통해 String 변수에 값을 할당하게 되면 일반적인 객체와 동일하게 Heap 영역에 동적으로 메모리 공간이 할당되게 됩니다. 마찬가지로 같은 문자열이더라도 new 키워드를 한번 더 사용하게 되면 같은 값이지만 서로 다른 메모리 공간(Heap 영역)을 참조하게 됩니다. 그래서 str3==str4 에서 false를 출력하게 되는 것입니다. 그리고 str1==str3 에서 false 를 출력하는 것은 서로 다른 메모리 공간을 참조하기 때문입니다.
String 객체는 한번 생성되면 할당된 공간이 변하지 않지만 StringBuffer나 StringBuilder의 경우 객체의 공간이 부족해지는 경우 버퍼의 크기를 늘려줍니다. 그래서 String은 불변(immutable)이고 StringBuffer와 StringBuilder는 가변(mutable)이라고 합니다.
※ 컴퓨팅에서, 버퍼(buffer, 문화어: 완충기억기)는 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역이다. 버퍼링(buffering)이란 버퍼를 활용하는 방식 또는 버퍼를 채우는 동작을 말한다. 다른 말로 '큐(Queue)'라고도 표현한다.
public class Main {
public static void main(String[] args) {
String str = "string first";
StringBuilder stringbuilder = new StringBuilder();
StringBuffer stringbuffer = new StringBuffer();
stringbuilder.append("stringbuilder first");
stringbuffer.append("stringbuffer first");
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+stringbuilder.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+stringbuffer.hashCode());
str += "string second";
stringbuilder.append("stringbuilder second");
stringbuffer.append("stringbuffer second");
System.out.println("----------------------------------------------");
System.out.println("string 객체의 주소 : "+str.hashCode());
System.out.println("stringbuilder 객체의 주소 : "+stringbuilder.hashCode());
System.out.println("stringbuffer 객체의 주소 : "+stringbuffer.hashCode());
}
}
결과 출력
String 객체의 주소 : -1392429599
StringBuilder 객체의 주소 : 856419764
StringBuffer 객체의 주소 : 621009875
----------------------------------------------
string 객체의 주소 : -1268750558
stringbuilder 객체의 주소 : 856419764
stringbuffer 객체의 주소 : 621009875
Process finished with exit code 0
String은 불변(immutable)이기 때문에 값이 변하면, 새롭게 객체가 만들어지고, StringBuffer와 StringBuilder는 가변(mutable)이라서 값이 변해도 기존 객체를 활용할 수 있어서, String의 객체의 주소만 바뀐 것을 확인할 수 있습니다.
StringBuffer와 StringBuilder의 클래스부터 살펴보겠습니다.
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
두 클래스 모두 AbstractStringBuilder 라는 추상 클래스를 상속받습니다.
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len);
asb.getChars(0, len, value, count);
count += len;
return this;
}
위 코드는 AbstractStringBuilder에 구현된 append 코드 입니다. StringBuilder , StringBuffer에 문자열을 추가하게 되면 ensureCapacityInternal(count + len)를 통해 추가된 문자열의 크기만큼 문자열을 저장하는 배열의 공간을 늘려주고, 늘려준 공간에 추가할 문자열을 넣어주는 방식입니다. 그래서 값이 변경되도 같은 주소공간을 참조하고, 값이 변경되게 됩니다.
StringBuffer와 StringBuilder 클래스는 가변적인 특성을 가지고 있으며 제공하는 메서드도 같고 사용하는 방법도 동일합니다. 그러나 두 클래스는 동기화 지원의 유무가 다릅니다. StringBuffer는 각 메서드 별로 synchronized keyword가 존재하여 멀티 쓰레드 상태에서 동기화를 지원하고 StringBuilder는 단일 쓰레드 환경에서만 사용하도록 설계되어 있습니다. synchronized는 여러 쓰레드가 한 객체에 접근할려고 할 때, 현재 데이터를 사용하고 있는 쓰레드를 제외하고 나머지 쓰레드들이 데이터에 접근할 수 없도록 막는 역할을 수행합니다.
예를 들어보겠습니다.
멀티쓰레드 환경에서 A쓰레드와 B쓰레드 모두 같은 StringBuffer 클래스 객체의 append() 메서드를 사용할 때 입니다.
A 쓰레드 : sb의 append() 동기화 블록에 접근 및 실행
B 쓰레드 : A 쓰레드 sb 의 append() 동기화 블록에 들어가지 못하고 block 상태가 됨.
A 쓰레드 : sb의 append() 동기화 블록에서 탈출
B 쓰레드 : block 에서 running 상태가 되며 sb 의 append() 동기화 블록에 접근 및 실행.
1. String
String 은 불변성을 갖습니다. 그래서 변하지 않는 문자열을 자주 사용할 경우 String 타입을 사용하는 것이 성능면에서 유리합니다
2. StringBuilder
StringBuilder는 동기화를 지원하지 않는 반면, 속도면에선 StringBuffer 보다 성능이 좋습니다. 그래서 단일 쓰레드 환경 과 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuilder를 사용하는 것이 성능면에서 유리합니다.
3. StringBuffer
StringBuffer는 동기화를 지원하여 멀티 쓰레드 환경에서도 안전하게 동작할 수 있습니다. 그래서 멀티 쓰레드 환경의 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuffer를 사용하는 것이 성능면에서 유리합니다.
https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)