Java/문자열

[Java]StringBuilder, StringBuffer 차이점

DevStory 2022. 9. 10.

StringBuilder, StringBuffer

StringBuilder와 StringBuilder는 변경 가능(mutable)한 속성을 가지는 클래스이며, 문자열을 버퍼로 관리합니다. 문자열을 추가, 변경, 삭제하는 경우 Heap 영역에 새로운 공간을 할당하지 않고 기존 공간의 크기를 변경합니다.

 

String과 달리 불필요한 메모리 공간을 생성하지 않으므로 GC(가비지 컬렉션)이 처리해야 하는 작업이 줄어듭니다.

 

이번 포스팅은 어디까지나 StringBuilder 클래스와 StringBuffer 클래스의 차이점을 소개하는 게 목적이므로 String에 대해 언급하지 않습니다.

 

StringBuilder, StringBuffer 클래스의 동작 방식, String과 차이점에 대한 내용은 아래 포스팅에서 확인할 수 있습니다.

 

[Java]StringBuilder 클래스 사용 방법

StringBuilder 클래스 사용 방법 Java에서 제공하는 StringBuilder 클래스는 단일 스레드에서는 안전하지만, 멀티 스레드에서는 불안전한 클래스입니다. StringBuilder 클래스는 문자열을 추가, 삭제, 변경할

developer-talk.tistory.com

 

[Java]StringBuffer 클래스 사용 방법

StringBuffer 클래스 사용 방법 Java에서 제공하는 StringBuffer 클래스는 멀티 스레드에서 안전한 클래스입니다. 여러 스레드가 StringBuffer 객체에 접근할 수 없기 때문입니다. StringBuffer 클래스는 문자열

developer-talk.tistory.com


StringBuilder, StringBuffer 차이점

StringBuilder 클래스와 StringBuffer 클래스의 차이점은 크게 세 가지입니다.


1. Java 버전

StringBuffer 클래스가 Java 1.0 버전에 도입되었으며, StringBuilder 클래스는 Java 1.5 버전에 도입되었습니다. StringBuilder 클래스가 늦게 도입된 이유는 StringBuffer 클래스가 멀티 스레드 환경에서 안전성을 제공하지만, 많은 비용을 소모하므로 속도가 느립니다.

 

이러한 StringBuffer 클래스의 단점을 보완하여 Java 1.5 버전에 StringBuilder 클래스가 도입되었습니다.


2. 스레드 환경

StringBuffer 클래스는 멀티 스레드 환경에서 안전하지만, 단일 스레드 환경에서 안전하지 않습니다.

반면에, StringBuilder 클래스는 단일 스레드 환경에서 안전하지만, 멀티 스레드 환경에서 안전하지 않습니다.

 

StringBuffer 클래스의 내부를 살펴보면, synchronized 키워드를 사용하여 메서드를 선언합니다.

반면, StringBuilder 클래스에서 제공하는 메서드는 synchronized 키워드가 존재하지 않습니다.

synchronized 키워드를 사용하면, 여러 스레드가 특정 자원(또는 객체)을 접근할 때, 단 하나의 스레드만 해당 자원을 접근할 수 있습니다. 따라서, StringBuffer는 멀티 스레드 환경에서 안전한 것이고 synchronized 키워드가 없는 StringBuilder는 멀티 스레드 환경에서 안전하지 않습니다.


3. 속도

StringBuilder와 StringBuffer는 스레드 환경에서 다르게 동작한다고 언급했습니다. synchronized 키워드를 사용하는 StringBuffer 클래스는 StringBuffer보다 속도 및 성능이 떨어집니다.

 

synchronized 키워드로 선언된 메서드는 Java 내부에서 동기화 작업을 거칩니다. 즉, StringBuffer는 동기화 작업을 거치는데, 비용 및 시간을 소모하므로 StringBuilder보다 성능 및 속도가 떨어집니다.


스레드 예제

다음 예제는 멀티 스레드 환경에서 StringBuilder, StringBuffer가 어떻게 동작하는지 보여줍니다.

 

[테스트 방법]

순서 1. Thread 클래스를 상속받는 두 클래스를 생성합니다.

순서 2. StringBuilder, StringBuffer에 0부터 5까지 값을 추가하는 로직을 run() 메서드에 재정의합니다.

순서 3. main문에 첫 번째 순서에서 생성한 클래스의 객체를 각각 네 개 생성합니다.

순서 4. start() 메서드와 join() 메서드를 호출하고 StringBuilder, StringBuffer 객체의 값을 확인합니다.

 

[StringBuilderThreadTest 클래스]

public class StringBuilderThreadTest extends Thread{
  StringBuilder _stringBuilder;

  public StringBuilderThreadTest (StringBuilder sb) {
    _stringBuilder = sb;
  }

  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      _stringBuilder.append(i);
    }
  }
}

[StringBufferThreadTest 클래스]

public class StringBufferThreadTest extends Thread{
  StringBuffer _stringBuffer;

  public StringBufferThreadTest (StringBuffer sb) {
    _stringBuffer = sb;
  }

  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      _stringBuffer.append(i);
    }
  }
}

[main문]

public static void main(String args[]) throws InterruptedException {
  StringBuffer stringBuffer = new StringBuffer();
  StringBufferThreadTest bufferThread = new StringBufferThreadTest(stringBuffer);
  StringBufferThreadTest bufferThread2 = new StringBufferThreadTest(stringBuffer);
  StringBufferThreadTest bufferThread3 = new StringBufferThreadTest(stringBuffer);
  StringBufferThreadTest bufferThread4 = new StringBufferThreadTest(stringBuffer);
  bufferThread.start();
  bufferThread2.start();
  bufferThread3.start();
  bufferThread4.start();
  bufferThread.join();
  bufferThread2.join();
  bufferThread3.join();
  bufferThread4.join();
  System.out.println("StringBuffer:" + stringBuffer.toString());

  StringBuilder stringBuilder = new StringBuilder();
  StringBuilderThreadTest builderThread = new StringBuilderThreadTest(stringBuilder);
  StringBuilderThreadTest builderThread2 = new StringBuilderThreadTest(stringBuilder);
  StringBuilderThreadTest builderThread3 = new StringBuilderThreadTest(stringBuilder);
  StringBuilderThreadTest builderThread4 = new StringBuilderThreadTest(stringBuilder);
  builderThread.start();
  builderThread2.start();
  builderThread3.start();
  builderThread4.start();
  builderThread.join();
  builderThread2.join();
  builderThread3.join();
  builderThread4.join();
  System.out.println("StringBuilder:" + stringBuilder.toString());
}

[첫 번째 실행 결과]

StringBuffer:00112233440123401234
StringBuilder:01234012340123401234

[두 번째 실행 결과]

StringBuffer:01234012340123401234
StringBuilder:01234012340123401234

스레드를 사용하는 정말 간단한 소스 코드라서 원하는 결과가 안 나올 수 있습니다. 하지만, 첫 번째 실행 결과를 보면 StringBuffer가 단일 스레드 환경에서 안전하지 않다는 것을 확인할 수 있습니다.


속도 테스트

다음 예제는 StringBuilder가 StringBuffer보다 빠르게 동작하는 것을 보여줍니다. StringBuffer, StringBuilder 객체를 생성하고 동일한 문자열을 반복문을 사용하여 추가합니다.

 

[테스트 방법]

순서 1. 현재 시각을 밀리 세컨드로 가져옵니다.

순서 2. StringBuffer에 반복문을 사용하여 동일한 문자열을 1억 번 추가합니다.

순서 3. 수행 시간을 구한 뒤 콘솔에 출력합니다.

순서 4. StringBuilder도 동일한 방법으로 테스트합니다.

 

반복문은 100,000,000번 수행합니다. (반복문 수행 횟수를 1억으로 설정한 이유는 10,000,000번 미만인 경우 큰 차이가 없었기 때문입니다.)

public static void main(String args[]) {
  long startTime = System.currentTimeMillis();
  
  StringBuffer stringBuffer = new StringBuffer("Start");
  for (int loop = 0; loop < 100000000; loop++){
    stringBuffer.append("Hello World");
  }
  System.out.println("StringBuffer for문 수행 시간: " +
          (System.currentTimeMillis() - startTime) + "ms");

  startTime = System.currentTimeMillis();
  StringBuilder stringBuilder = new StringBuilder("Start");
  for (int loop = 0; loop < 100000000; loop++){
    stringBuilder.append("Hello World");
  }
  System.out.println("StringBuilder for문 수행 시간: " +
          (System.currentTimeMillis() - startTime) + "ms");
}

[첫 번째 실행 결과]

StringBuffer for문 수행 시간: 6082ms
StringBuilder for문 수행 시간: 4593ms

[두 번째 실행 결과]

StringBuffer for문 수행 시간: 5452ms
StringBuilder for문 수행 시간: 4940ms

[세 번째 실행 결과]

StringBuffer for문 수행 시간: 5362ms
StringBuilder for문 수행 시간: 4917ms

위 예제의 실행 결과는 StringBuilder가 StringBuffer보다 빠른 것을 증명합니다.

반응형

댓글