String, StringBuffer, StringBuilder 어떻게 다른가?

박진형·2022년 3월 22일
0

개요

JAVA에서는 문자열을 다루는 클래스로 String, StringBuffer, StringBuilder가 있다.
하지만 이 세 가지는 각각 차이점을 가지고 있는데, 각각의 차이점을 이해하고 사용할 줄 알아야한다. 이 글에서는 그 차이점에 대해서 정리하고자 한다.

String

String은 불변 객체(immutable)라고 한다. 이 String 객체는 어떻게 선언을 하냐에 따라 JVM내의 String constant pool에 저장되기도하고, Heap 영역에 저장되기도 한다.

1. 리터럴을 이용한 방식

String str = "Hello";

리터럴 방식으로 생성하게 되면 String constant pool에 저장되고 내부적으로 intern()이라는 함수가 동작한다.
intern()은 String constant pool을 뒤져 같은 내용의 문자열이 있는지 찾는다.
같은 내용의 문자열이 있다면 그 문자열의 주소값을 반환해서 재사용을 한다.
없다면 String constant pool에 새로 할당을 하고 그 주소 값을 반환을 한다.

즉, 아래와 같이 str과 str2를 같은 내용으로 리터럴방식으로 선언을 한다면 같은 주소값을 가진다는 뜻이다.

String str = "Hello";
String str2 = "Hello";

그렇다면 위의 코드에서 str과 str2는 같은 주소값을 가르키기 때문에
str == str2 는 true를 반환한다. 그렇기 때문에 리터럴로 생성된 문자열을 비교할 때 str.equals(str2)로 굳이 비교를 하지 않아도 되는 이유이다.

2. new 연산자를 이용한 방식

반면에 new 연산자를 이용한 방식은 Heap영역에 별개의 인스턴스가 생성되어 별개의 주소값을 가르킨다.

String str = new String("hello");

JAVA7버전 전에는 String constant pool이 Perm영역에 속해 있었고 이는 GC의 대상이 아니었기 때문에 OOME 문제가 빈번히 발생할 수 있었다.
하지만 JAVA7버전 부터는 String constant pool이 Heap 영역으로 이동하게 되면서 GC의 대상이 되어 그 문제가 어느정도 해소 되었다.
참고 - JAVA 8에서 perm 영역이 사라지고 metaspace 영역으로 대체된 이유?

리터럴로 선언한 String을 설명할 때 내부적으로 intern() 메소드가 동작한다고 말했다. 그러면 실제로 테스트 코드를 돌려보면서 내가 설명한 내용이 맞는지 확인해봤다.

 @Test
  public void intern_test(){
    String str1 = new String("d");
    String str2 = "a";
    String str3 = "a";
    Assertions.assertTrue(str2 == str3);
    Assertions.assertFalse(str1 == "d");
    Assertions.assertTrue(str1.intern() == "d");
  }


먼저 같은 내용의 리터럴 문자를 == 으로 비교를 해서 참으로 나오는지 확인을하고,
같은 내용이지만 하나는 리터럴, 하나는 new 연산자로 생성한 문자열을 == 으로 비교해서 다른 것을 확인을 한 후, new 연산자로 생성한 문자열에 intern() 메소드를 실행시켜 주고 난 후 과연 같은 내용의 리터럴 문자열과 비교했을 때 참으로 나올까?에 대해서도 확인해봤다.

추가적으로 설명하자면, String은 한번 선언이 되면 변하지 않는 특성을 가진다.
아래와 같이 문자열을 수정하고자 하면, 실제로는 문자열이 수정되는 것이 아닌, 새로운 문자열이 할당되는것이다.

이러한 방법은 문자열 추가, 수정, 삭제가 빈번하게 발생한다면 많은 Garbage가 발생되어 힙 영역의 메모리 부족으로 이어질 수 있다.

그러므로 문자열의 변동이 잦은 경우에는 다른 방법을 사용해야한다.

StringBuilder, StringBuffer

위의 문제를 개선하기 위해 StringBuilder와 StringBuffer 클래스를 사용할 수 있다.
이 클래스는 mutable하기 때문에 문자열의 추가, 수정, 삭제에 비교적 유리하다.
여기서도 이 둘의 차이가 있는데 그것은 바로 thread-safe에서 차이점을 보인다.

StringBuffer

StringBuffer 클래스는 내부에 동기화 키워드를 사용하기 때문에 멀티 쓰레드 환경에서 안전하다.

StringBuilder

StringBuilder는 클래스 내부에 동기화 키워드를 사용하지 않기 때문에 멀티 쓰레드 환경에서 사용하기 적합하지 않다.

대신, StringBuilder는 thread-safe한 StringBuffer보다 성능이 비교적 빠르다.

결론

즉, 문자열의 변동이 잦다면 String보다는 StringBuffer, StringBuilder 클래스를 사용하고 추가적으로, 현재 환경이 멀티 쓰레드 환경인지, 단일 쓰레드 환경인지에 따라 어떤 문자열 클래스를 사용할지 선택하는 것이 좋다.

0개의 댓글