본문 바로가기

JAVA

StringBuffer,StringBuilder의 개념 및 차이점 정복하기

들어가기 전

혹시 누군가 둘의 차이에 대해 물어본다면 이렇게 답변하실수 있으신가요??

StringBuilder와 StringBuffer는 모두 문자열을 변경하거나 추가하는 연산을 제공하는 가변성 클래스입니다<br>
이 둘을 가르는 차이점은 동기화에 있습니다<br>
StringBuffer는 각 메서드에 대한 동기화를 지원하므로 멀티쓰레드 환경에서 안전합니다.<br>
즉 여러 스레드가 동시에 객체를 변경할 수 없으므로, 멀티스레드 환경에서 사용하면 좋습니다<br>
반면 StringBuilder는 동기화를 지원하지 않습니다. 하지만 단일 스레드 환경일 시 더 빠른 성능을 보입니다.<br>

이렇게 답변하시기 어려우시다면 저랑 함께 이 둘의 개념과 차이점에 대해 알아보겠습니다!!

 

StringBuffer/ StringBuilder 클래스란?

StringBuffer와 StringBuilder 클래스는 문자열을 연산(추가하거나 변경)할때 주로 사용하는 자료형 입니다!

 

물론 저희가 기존에 사용하던 방식처럼 +연산이나, concat() 메서드를 통해 문자열을 이어 붙일 수 있습니다.

하지만 덧셈(+) 연산자를 이용하여 String 인스턴스의 문자열을 결합하면, 내용이 합쳐진 새로운 String 인스턴스를 생성하게 되어, 문자열을 많이 결합할수록 메모리 공간 낭비 뿐 아니라 속도 또한 느려지게 됩니다!

 

따라서 자바에서는 이러한 문자열 연산을 전용으로 하는 클래스를 제공해주는데, 이 것이 StringBufferStringBuilder입니다.

 

기본적으로 StringBuffer의 버퍼(임시 데이터 공간) 크기는 16개의 문자를 저장할 수 있는 크기입니다.(char[] 사용, 이는 생성자를 통해 크기를 별도로 지정 가능)

만약 문자열 연산 중에 할당된 버퍼의 크기를 넘게 되면 자동으로 버퍼를 증강시키니 걱정하지 않아도됩니다!

 

즉, StringBuilder와 StringBuffer는 문자열을 추가하거나 변경할때, 새로운 객체를 생성하는 것이 아니라 기존 객체를 직접 변경하게 됩니다!

 

이러한 두 클래스는 불변의 특성을 가진 String과 다르게 가변적이라는 거겠죠??

 append()메서드를 통해 값을 추가할 수 있습니다!

위 그림을 보시면 새로운 객체를 만들어 문자열을 합치는 String과는 다르게 동작하는 것을 볼 수 있습니다! 즉 동일한 객체 내에서 문자열을 변경하는 것이 가능하다는 것이죠

 

아래 코드는 별 문자를 루프문을 순회할때마다 추가하여 긴 별 문자열을 만드는 예제입니다.

 

String star = "*";

for(int i = 1; i < 10; i++){
	start += "*";
}

StringBuffer sb = new StringBuffer("*");
sb.append("*********");

 

이 둘의 차이를 마지막으로 그림을 통해 좀더 직관적으로 확인해보겠습니다

 

String 객체의 경우 매번 별 문자열이 업데이트 될때마다 계속해서 메모리가 할당되고, 일회용으로 사용된 메모리들은 후에 Garbage Collector의 제거 대상이 되어 빈번하게 Minor GC를 일으켜 Full GC(Major GC)를 일으킬 수 있는 원인이 됩니다.

 

반면 StringBuffer는 자체 메모리에서 문자열을 늘이고, 즐일수 있기 때문에 문자열 데이터를 더 효율적으로 다룰 수 있습니다.

 

그리고 String은 equals()메서드를 통해 데이터가 같은지 비교할 수 있다고 했던것 기억하시나요??

하지만 StringBuffer와 Builder는 equals() 메서드를 오버라이딩 하지 않아 == 로 비교한것과 같은 결과를 얻게 된다는 점도 참고해주세요

 

그렇다면 무조건 String 대신 StringBuffer나 StringBuilder를 쓰는게 좋을까요?? -> 이는 상황에 따라 다르다고 볼 수 있습니다.

StringBuffer나 StringBuilder의 경우 buffer 크기를 초기에 설정해주어야 하기에, 동작이 비교적 무거운 편이고, 문자열 수정을 할때 버퍼의 크기를 늘리고 줄이고 명칭을 변경해야 하는 내부적인 연산이 필요하므로, 자주 문자열을 수정하는 것이 아니라면 String 객체를 사용하는 것이 오히려 나을 수도 있습니다!

 

또한 String 클래스는 크기가 고정되어 있으므로 단순하게 조회하는 연산에서는 더 빠를 수 있습니다


StringBuffer와 StringBuilder의 차이점

둘의 차이점은 딱 한가지입니다! 바로 멀티 쓰레드 상황에서 Safe한지 아닌지 입니다

StringBuffer 클래스는 쓰레드에서 안전(Thread Safe)한 반면, StringBuilder 클래스는 쓰레드에서 안전하지 않습니다(Thread unSafe)

이는 두 클래스의 동기화(Synchronization) 지원 유무가 다르기 때문입니다.-> (StringBuffer 클래스는 메서드에서 synchronized 키워드 사용)

 

Synchronized 키워드는 여러개의 스레드가 한 자원에 접근할려고 할때, 현재 데이터를 사용하고 있는 쓰레드를 제외하고 나머지 쓰레드들이 데이터에 접근하지 못하게 해주는 것입니다!

 

위 말이 잘 와닿지 않으신다면 아래 코드를 꼼꼼히 보신다면 좀더 이해하시기 편할 것입니다!

import java.util.*;

public class Main extends Thread{
  public static void main(String[] args) {
    StringBuffer stringBuffer = new StringBuffer();
    StringBuilder stringBuilder = new StringBuilder();

    new Thread(() -> {
        for(int i=0; i<10000; i++) {
            stringBuffer.append(1);
            stringBuilder.append(1);
        }
    }).start();

    new Thread(() -> {
        for(int i=0; i<10000; i++) {
            stringBuffer.append(1);
            stringBuilder.append(1);
        }
    }).start();

    new Thread(() -> {
        try {
            Thread.sleep(2000);

            System.out.println("StringBuffer.length: "+ stringBuffer.length()); // thread safe 함
            System.out.println("StringBuilder.length: "+ stringBuilder.length()); // thread unsafe 함
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
  }
}

// 출력 결과
StringBuffer.length : 20000
StringBuilder.length : 19628

동시에 append()를 할려다가 몇번은 제대로 수행되지 않음(Builder)

비동기 상황에서는 StringBuffer를 사용하는 것이 효과적!

 

참고

자바의 정석

https://inpa.tistory.com/entry/JAVA-☕-String-StringBuffer-StringBuilder-차이점-성능-비교#recentEntries