String vs StringBuffer vs StringBuilder
String Class
public final class String implements Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
static final boolean COMPACT_STRINGS = true;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
public String() {
this.value = "".value;
this.coder = "".coder;
}
@HotSpotIntrinsicCandidate
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
public String(char[] value) {
this((char[])value, 0, value.length, (Void)null);
}
String 클래스의 일부를 살펴보면 final byte[] 로 값을 다루고 있는 걸 볼 수 있습니다.
Serializable이라는 인터페이스를 상속받고 있는데, 처음 접해보는 인터페이스라 찾아보니 직렬화와 관련된 인터페이스라고 한다.
직렬화라는 단어를 많이 들어보긴 했지만 자세히 알진 못한다. 찾아보니 자바에서 직렬화란 객체 데이터를 byte형태로 변환하는 것을 의미한다고 한다.
String 클래스는 불변 객체이다. 한번 생성된 인스턴스가 갖고 있는 문자열 값은 여러 번 읽을 수는 있지만 변경은 불가능하다.
/* 문자열 리터럴 */
String str1 = "ABC";
String str2 = "ABC";
/* String 생성자 */
String str3 = new String("ABC");
String str4 = new String("ABC");
문자열 리터럴 방식으로 생성 했을시 클래스가 메모리에 로드될 때 자동 생성되어 이미 존재하는 동일 리터럴이 있다면 재사용한다.
이 때문에 같은 내용은 문자열을 하나의 메모리를 같이 이용한다.
new String 생성 방식에서는 new 키워드답게 같은 내용의 문자열이라도 새로운 주소를 할당받아 생성한다.
str1==str2//true
str1==str3//false
str3==str4//false
str2.equals(str3);//true
str3.equals(str4);//true
StringBuilder vs StringBuffer
StringBuffer 클래스는 Java 1.4 전까지 String을 조작하는 유일한 방법이었습니다. 하지만 모든 public method가 동기화가 되었고 Thread safety 했지만 그만큼 비용이 많이 들어가기도 했습니다. 하지만 대부분 멀티스레드 환경에서 String을 사용하진 않았고 그래서 java 1.5부터 StringBuffer에서 Thread safety와 Synchronization(동기화)를 제외한 StringBuilder가 추가되었습니다.
단일 스레드를 사용하거나 thread safe에 상관없는 상황이라면 StringBuilder 클래스를 추천하는데 이유를 알아보자.
public class TestString {
public static void main(String[] args) {
System.gc();
long start=new GregorianCalendar().getTimeInMillis();
long startMemory=Runtime.getRuntime().freeMemory();
StringBuffer sb = new StringBuffer();
//StringBuilder sb = new StringBuilder();
for(int i = 0; i<10000000; i++){
sb.append(":").append(i);
}
long end=new GregorianCalendar().getTimeInMillis();
long endMemory=Runtime.getRuntime().freeMemory();
System.out.println("Time Taken:"+(end-start));
System.out.println("Memory used:"+(startMemory-endMemory));
}
}
StringBuilder와 StringBuffer의 클래스로 Java11 에서실행 했을 때 결괏값은 아래와 표와 같았다.
Value of i | StringBuffer (Time : Memory) | StringBuilder (Time : Memory) |
10,000,000 | 258 : 2756224 | 254 : 2741696 |
단일 스레드 환경에서 StringBuffer보다 StringBuilder이 더 나은 성능을 보인다는걸 확인했다. 이러한 성능의 차이는 동기화의 차이라고 볼 수 있다.
정리
1. String은 변경 불가능하지만 StringBuffer와 StringBuilder는 변경 가능한 클래스이다.
2. StringBuffer 는 Thread-safe하고 동기화를 지원하지만 StringBuilder는 지원하지 않는다. (StringBuilder가 더 빠른이유)
3. 문자열 연결 연산자(+)는 내부적으로 StringBuffer 또는 StringBuilder 를 사용한다.
4. 멀티 스레드가 아닌 환경에서 문자열을 조작한다면 StringBuffer 보단 StringBuilder 를 사용하자!