본문 바로가기
DevCourse

String, StringBuffer, StringBuilder 의 차이

by 우롱추출물 2021. 8. 8.

String

자바 문자열(Java String) 은 프로그램에서 자주 사용되기 때문에 특별합니다.

따라서 효율성 (계산 및 저장 측면에서)이 중요합니다.

Java 디자이너는 언어의 성능을 향상시키기 위해 모든 것을 객체로 만드는 대신 객체 지향 언어에서 기본 타입(primitive type) 을 유지하기로 결정했습니다. 기본형 타입은 저장공간을 덜 필요로 하고 조작비용이 저렴한 call stack 에 저장이 됩니다. 반면, 개체(Object)는 Heap 메모리에 저장이 되어 복잡한 메모리 관리와 더 많은 저장공간을 필요로 합니다.

Java 의 String은 한번 생성되면 Immutable(불변) 하므로, 값을 변경할수가 없습니다.
(내부적으로 final이라는 키워드가 붙어 있기때문입니다)

String 을 생성하는 2가지 방법

 

String str1 = "Mini";                // 1. 문자열 리터럴의 암시적 생성 (String 리터럴)
String str2 = new String("Kang");    // 2. new를 통한 명시적 객체 생성  (String 객체)




  1. 상수풀(constant pool) 에 있는 주소를 참조.
  2. 힙메모리에 인스턴스로 생성하는 경우.

 

 

String 리터럴과 String Object 의 차이점을 코드로 살펴보면

String str1 = "Mini";
String str2 = "Mini";
String str3 = str1;
String str4 = new String("Mini");
String str5 = new String("Mini");

System.out.println(str1 == str2);    //true
System.out.println(str1 == str3);    //true
System.out.println(str3 == str4);    //false
System.out.println(str4 == str5);    //false



  • 위 코드를 간단히 그림으로 보면 아래와 같이 생각해볼 수 있습니다.

 

  • str1, str2, str3 상수풀의 "Mini" 라는 동일한 String 리터럴을 공유하고 있다.
  • 반면에 str4, str5는 힙영역에 각각의 인스턴스를만들고 다른 인스턴스를 참조하고 있다.
  • 따라서 코드의 결과가 아까처럼 나올 수 있었던 것이다.
  • String 객체를 생성하기 위해 new 연산자를 사용하는 것은 흔치않고, 권장하지 않는다. (메모리를 불필요하게 낭비할 수 있다.)
  • 동일한 문자열을 사용하고 문자열을 연산하지 않을때에는 상수풀에 String 리터럴로 만들어놓고 같은 내용을 참조하는게 전략적인듯하다.

 


 

StringBuilder vs StringBuffer

앞서 봤던 String 의 불변성(immutable)은 매우 유용합니다. 단 문자열 연산이 많은 경우 불변성이 상황을 악화시킵니다.

예를 들어, 문자열을 대문자로 만든 뒤 subString()을 하고싶다. 이럴 때 String은 불변성을 가지고 있는 클래스이므로 String 풀에 원본문자, 대문자로만든문자, subString으로 자른 문자 3개의 문자열을 가지게 됩니다. 새 문자열이 생성될 때마다 이전 문자열이 삭제되고, 힙 메모리영역에 많은 임시 쓰레기(garbage)가 생성됩니다. 이 문제를 해결하고자 Java의 StringBuffer 와 StringBuilder를 소개합니다.

두가지 모두 mutable(변경가능)하기 때문에, 이런 힙 메모리에 임시 쓰레기(garbage)가 발생하는 문제를 도울 수 있습니다.

그럼 StringBuilder와 StringBuffer 의 차이는 무엇일까요?

 

둘다 동일한 API를 가지고있고, toString() 메서드를 통해서 String 으로 변환이 가능 합니다.

만약, 두 문자열을 연결하려면 '+' 연산을 사용할 수 있습니다.

'+' 작업은 내부적으로 StringBuffer 또는 StringBuilder를 사용하여 내부적으로 구현되기 때문입니다.

 

그렇다면 왜 StringBuffer는 ThreadSafe할까?

  • stringbuffer의 모든 메서드에는 synchronized 키워드가 달려있습니다.
  • synchronized 키워드가 있음으로서 스레드간 동기화를 시켜 thread-safe하게 해줍니다.
  • 여러개의 스레드가 한개의 자원을 사용하려할 때,현재 데이터를 사용하는 해당 스레드를 제외하고 나머지 스레드는 데이터에 접근할 수 없도록 하는 개념.
  • 단, 너무 많이 동기화처리를 하게 되면 block, unblock이 많이 되면서 공수가 많이 들고, 프로그램 성능저하로 연결될 수 있습니다.

 

위처럼 StringBuffer는 멀티 쓰레드 환경에서도 (2개만 생성했지만) 결과가 동일함을 확인할 수 있었습니다.

StringBuilder는 반복문을 100개까지 돌릴때에는 결과가 동일했지만, 점차 반복문이 커질수록 값이 달라지는 것을 확인할 수 있었습니다.


정리

  • Stirng 은 불변성이 필요할 때 사용하자.
  • StringBuffer는 가변성(mutable) + thread-safe이 필요할때(multi thread사용시) 사용하자.
  • StringBuilder는 가변성(mutable) + thread-safe이 필요하지 않을 때(single thread) 사용하자.

StringBuffer는 Thread로부터 안전하고 동기화되므로, StringBuilder보다 성능은 나쁘다.