StringBuilder와 StringBuffer는 무슨 차이가 있는가?

HeoSeungYeon·2021년 8월 11일
23

Java Study

목록 보기
3/9
post-thumbnail

개요


Java에서 String 클래스불변성을 갖습니다. 그래서 변하지 않는 문자열을 자주 사용하는 경우엔 좋은 성능을 기대할 수 있습니다.
하지만 문자열에 대한 변경이 자주 일어나는 프로그램에서 String만 사용하게 된다면 효율적인 성능을 기대하기 어렵습니다. 그래서 우리는 StringBuilder , StringBuffer을 사용하여 효율적인 성능을 보일 수 있습니다.
언제 StringBuilder를 사용하면 좋을지 또 언제 StringBuffer을 사용하면 좋을지에 대해 살펴봅시다 👀

0. Java 문자열 대표 클래스


Java 에서 문자열을 다루는 대표적인 클래스는 String, StringBuilder, StringBuffer 가 있습니다. 3가지 클래스는 각자 차이점이 존재하는데요. 차이점에 맞게 다음과 같은 순서로 설명을 진행하도록 하겠습니다❗

  • String VS StringBuilder , StringBuffer
  • String Constant Pool
  • StringBuilder VS StringBuffer
  • String 을 사용해야 할 때
  • StringBuilder 를 사용 해야 할 때
  • StringBuffer 를 사용해야 할 때

1. String VS StringBuilder , StringBuffer


Java에서 String 객체는 한번 값이 할당되면 그 공간은 변하지 않습니다. 하지만 StringbuilderStringBuffer 객체는 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변하는 특성을 갖고 있습니다.

여기서 할당된 공간이 변하지 않는 특성을 불변(Immutable)성이라고 하고,

할당된 공간이 변하는 특성을 가변(mutable)성이라고 합니다.

그러면 다음과 같이 String과 StringBuilder, StringBuffer의 특성의 차이를 정리할 수 있습니다.

String

  • 불변성을 갖는다. → Immutable 하다.

StringBuilder, StringBuffer

  • 가변성을 갖는다. → mutable 하다.

좀 더 이해하기 쉽게 예시 코드를 통해 불변성과 가변성을 확인해보도록 하겠습니다.

가변성과 불변성 예시 코드

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은 어디에 있는 메모리를 참조하여 값을 갖고있을까요?
  • String 객체의 값이 변할 때마다 어떻게 동작할까요?

위 질문에 답변을 밑에서 하도록 하겠습니다‼️

1-1. String Constant Pool


String 변수에 값을 할당하는 방법은 2가지가 있습니다.

  • 리터럴 변수를 대입하는 방법
  • new 키워드를 사용하는 방법

위 두가지 방식을 사용하여 예시코드를 작성해보겠습니다.

String strA = "abc";
String strB = new String("abc");
String strC = "abc";
String strD = new String("abc");

System.out.println(strA==strB); //false
System.out.println(strA==strC); //true
System.out.println(strB==strD); //false

어라, 3개의 변수 모두 "abc"라는 문자열을 갖는데 3개의 주소비교(==)의 결과가 상이할까요?

그 이유는 String 타입 값 할당 방식에 따른 저장 방식이 다르기 때문입니다.

리터럴 값으로 값을 할당하는 경우


String 을 리터럴 값으로 할당하는 경우엔 Heap 메모리 영역안의 특별한 메모리 공간인 String constant pool 에 저장됩니다.

만약 String constant pool에 존재하는 리터럴 값을 사용하게 된다면 새롭게 리터럴 값을 만들어 String constant pool에 저장하는 것이 아닌, 현재 존재하는 값을 사용하게 됩니다.

위와 같은 이유로 System.out.println(strA==strC); 의 실행 결과가 true 가 나오게 되는 것입니다.

new 키워드로 값을 할당하는 경우


new 키워드를 통해 String 변수에 값을 할당하게 되면 일반적인 객체와 동일하게 Heap 영역에 동적으로 메모리 공간이 할당되게 됩니다.

마찬가지로 같은 문자열이더라도 new 키워드를 한번 더 사용하게 되면 같은 값이지만 다른 메모리 공간(Heap 영역 안)을 참조하게 됩니다.

위와 같은 이유로 System.out.println(strB==strD); 에서 false 를 출력하게 되는 것입니다.

또한 System.out.println(strA==strB); 에서도 false 를 출력하는 것도 서로 상이한 메모리 공간을 참조하기 때문입니다.

  • strA → Heap → String Constant Pool
  • strB → Heap

2. StringBuilder VS StringBuffer


String 과 달리 StringBuilderStringBuffer는 둘 다 크기가 유연하게 변하는 가변성을 갖는다고 우리는 배웠습니다.

위에 보이는 것 처럼 두 클래스 모두 AbstractStringBuilder 라는 추상 클래스를 상속받아 구현되어 있기 때문인데요.

AbstractStringBuilder 추상클래스의 멤버 변수엔 다음 2가지 변수가 존재합니다.

  • value : 문자열의 값을 저장하는 byte형 배열
    • The value is used for character storage.
  • count : 현재 문자열 크기의 값을 가지는 int 형 변수
    • 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() 메서드를 사용하려고 하면, 다음과 같은 절차를 수행하게 된다.

  • A 스레드 : sb의 append() 동기화 블록에 접근 및 실행
  • B 스레드 : A 스레드 sb 의 append() 동기화 블록에 들어가지 못하고 block 상태가 됨.
  • A 스레드 : sb의 append() 동기화 블록에서 탈출
  • B 스레드 : block 에서 running 상태가 되며 sb 의 append() 동기화 블록에 접근 및 실행.

StringBuilder 클래스 주석에서 동기화가 필요할 경우 StringBuffer을 추천한다는 문구를 확인할 수 있다.

3. String 을 사용해야 할 때


위에서 우리는 "String 은 불변성을 갖는다" 라는 것을 배웠습니다.

그렇기 때문에 우리는 변하지 않는 문자열을 자주 사용할 경우 String 타입을 사용하는 것이 성능면에서 유리할 것 입니다.

4. StringBuilder 를 사용 해야 할 때


StringBuilder는 동기화를 지원하지 않는 반면, 속도면에선 StringBuffer 보다 성능이 좋습니다.

그렇기 때문에 우리는 단일 스레드 환경문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuilder를 사용하는 것이 성능면에서 유리할 것입니다.

5. StringBuffer 를 사용해야 할 때


StringBuffer는 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작할 수 있습니다.

그렇기 때문에 우리는 멀티 스레드 환경문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuffer를 사용하는 것이 성능면에서 유리할 것입니다.

참고문서


[Java] String, StringBuffer, StringBuilder 차이 및 장단점

[Java] String, StringBuffer, StringBuilder의 차이점과 사용이유

Java - Constant pool과 String pool - 조금 늦은, IT 관습 넘기 (JS.Kim)

String Constant Pool in Java - GeeksforGeeks

StringBuffer, StringBuilder 가 String 보다 성능이 좋은 이유와 원리

1개의 댓글

comment-user-thumbnail
2023년 7월 6일

이해가 잘 되게 설명해주셔서 감사합니다! 많은 지식 얻고 갑니다.

답글 달기