(Java) String vs StringBuilder vs StringBuffer

Yong Lee·2025년 3월 5일

Java에서 문자열을 핵심적으로 다루는 클래스는 String, StringBuilder, StringBuffer입니다.

String만 사용해도 문자열을 만드는데 지장은 없을 수 있습니다.
하지만, 이번 시간에 이것들이 대체 무엇인지, 왜 필요한지 알아보겠습니다.


String이란?

String은 문자열 중 가장 많이 사용됩니다.
어떤 특징들 때문에 사용되는 걸까요?

String의 특징

  • 불변(Immutable) 객체
  • 문자열 수정 시 새로운 객체가 생성됨
  • 성능보다 안정성이 중요한 경우에 적합

불변이란 한 번 생성된 객체의 값이 변경되지 않는 것을 의미합니다.
String 객체는 내부적으로 변경할 수 없는 char[] 배열을 가지고 있으며, 한 번 생성되면 그 값을 바꿀 수 없습니다.

public class StringTest {
    public static void main(String[] args) {
        String str = "Hello";
        str = str + " World";

        System.out.println(str); // "Hello World"
    }
}

간단한 예시 코드를 가져왔습니다.
이 코드에서 str이라는 변수에 "Hello"라는 데이터를 넣은 상태입니다.
그리고 str 변수에 " World"라는 데이터를 집어넣어서
str을 프린트했을 때 "Hello World"가 되었습니다.

보기엔 값이 변한 것처럼 보이지만 사실 새로운 str이라는 문자열 객체를 만든 것입니다.

원시타입(Primitive Type)은 소문자로 시작합니다.
예) int, double, long, char 등등

하지만 문자열은 항상 'String' 대문자로 시작합니다.
이유는 String은 참조타입이기 때문입니다.
참조타입은 Heap 영역에 데이터를 저장합니다.

Stack 영역에서 주소값을 들고, Heap 영역에서 주소값과 일치하는 데이터 가져와 사용하는 방식입니다.

한발 더 나아가 알아보자면 String 객체는 특별히 String constant pool에서 따로 관리가 됩니다.

🔹 String Constant Pool이란?
String 객체는 일반적인 객체처럼 Heap 메모리에 생성될 수도 있지만, "String Constant Pool"이라는 별도의 공간에서 관리됩니다.
이는 메모리 사용을 최적화하고, 불필요한 중복을 줄이기 위해서 입니다.

다시 Heap으로 돌아가자면, 0x11(그림) 주소값에 있는 데이터는 stack에서 바라보는 존재가 없기 때문에 GC에서 제거해줍니다.

String의 추가 or 변경 발생하는 일
=> 새로운 객체 생성, GC에서 이전 주소값의 데이터 제거

이러한 이유 때문에 변경에는 성능적으로 좋지 않습니다.

그럼 어떤 장점때문에 많이 사용되는 걸까요?

1. 불변성(Immutable)으로 인한 안정성

  • 문자열을 수정하는데에는 굉장히 비효율적으로 보였지만, 이 불변 덕분에 멀티스레드 환경에서도 안전하게 공유 가능하고, 데이터 무결성을 유지할 수 있습니다.

2. 메모리 효율적인 String Pool 사용

  • Java는 문자열 리터럴을 String Pool(문자열 상수 풀)에 저장하여 메모리 낭비를 줄입니다. 같은 문자열 리터럴을 여러 번 선언해도 새로운 객체를 만들지 않고 기존 객체를 재사용합니다.
String str1 = "Java";
String str2 = "Java";

System.out.println(str1 == str2); // true (같은 객체를 참조)

3. 직관적이고 사용하기 쉬운 메서드 제공

  • Java에서는 다양한 String의 메소드를 제공하고 있습니다. (W3Schools 메서드는 여기서 참고)
    원래라면 직접 코드를 해야하는 기능을 쉽고 빠르게 만들 수 있는 강점이 있습니다.

4. String은 다양한 API에서 필수적으로 사용

  • 파일 입출력 (I/O)
    • FileReader, BufferedReader 등을 사용할 때 String을 통해 데이터를 읽고 씁니다.
  • 네트워크 통신
    • HTTP 요청 및 응답, URL, JSON/XML 데이터 등 대부분의 네트워크 데이터는 String으로 다룹니다.
  • 데이터베이스 처리
    • SQL 쿼리 문자열을 다룰 때 String이 사용됩니다.
  • Java 기본 API 연동
    • System.out.println() 같은 출력 메서드에서도 String을 기본적으로 사용됩니다.

StringBuilder와 StringBuffer를 알아보자

위에서 기본이라고 할 수 있는 String에 대해서 알아봤습니다.
그럼 StringBuilder와 StringBuffer는 어떠한지 알아보겠습니다.

StringBuffer와 StringBuilder 클래스가 가진 큰 특징은 String과는 다르게 가변(mutable) 문자열을 나타냅니다.
위에서 얘기한 것처럼 String은 char[]이라는 배열에 있습니다.
이 StringBuffer와 StringBuilder도 char[] 배열로 이루어져있습니다.

그런데 여기서 의문이 생깁니다.
똑같은 배열이고, 자바에서 배열은 크기를 지정해줍니다.
그런데 어떻게 효과적으로 배열의 크기를 늘릴 수 있었을까요?

ArrayList와 굉장히 유사합니다.
거의 문자열 특화로된 ArrayList라고 할 수 있다고 생각합니다.
유사한 특징들을 나열해보겠습니다.

  • 내부적으로 동적 배열을 사용 → 필요할 때 크기를 확장함.
  • 배열 크기를 초과하면 새로운 배열을 만들고 기존 데이터를 복사 → 성능 최적화 방식이 비슷함.
  • Heap 영역에 저장 → 실제 데이터(char[] 또는 Object[])는 Heap에 위치함.
  • 새로운 객체를 생성하지 않고, 기존 객체에서 데이터를 수정 가능 → 메모리 효율성이 높음.

String은 수정 혹은 추가할 때, 매번 새로운 객체 만들었습니다.
하지만 StringBuilder와 StringBuffer는 크기가 부족할 때만, 새로운 객체를 만듭니다.
이러한 이유로 문자열이 변동이 심한 곳에는 StringBuilder와 Buffer를 쓰는게 효율적입니다.

StringBuilder vs StringBuffer

여지껏 보기엔 둘은 다를게 없어보이는데 왜 두개를 따로 만들었을까요?
StringBuffer와 StringBuilder의 주요 차이점은 동기화(synchronization) 지원 여부때문입니다.

동기화란?

동기화(synchronization)는 여러 스레드가 동시에 접근할 때 발생할 수 있는 문제를 해결하기 위한 방법입니다. 여러 스레드가 동시에 공유 자원에 접근하여 데이터를 수정하거나 읽을 때, 데이터의 일관성이 깨질 수 있는 문제가 생깁니다. 동기화는 이러한 문제를 방지하고, 정상적인 순서대로 실행되도록 보장하는 기술입니다.

특성StringBuilderStringBuffer
동기화🟢
멀티쓰레드 안정성🟢
성능상대적으로 빠름상대적으로 느림
사용처싱글 쓰레드 환경멀티쓰레드 환경

위에 표를 보만 봐도, 만약 프로그램을 만들었을 때 어떤 쓰레드 환경인지 알아야겠다는 생각을 할 수 있습니다.
제대로 파악하지 못한다면 분명 여러 트러블이 생길 것은 자명합니다.


마무리

지금까지 String, StringBuffer, StringBuilder 클래스에 대해 알아보았습니다.
각자의 특성이 있고, 상황에 따라 좋을 수도 나쁠 수도 있다는 것을 알았습니다.
자신의 프로젝트의 환경을 이해하고, 상황에 따라 적절히 사용하여 좋은 서비스를 만들기 바랍니다.


참고자료

F-Lab 자바에서의 문자열 처리: String, StringBuffer, StringBuilder의 이해와 활용

Java의 String 이야기(1) - String은 왜 불변(Immutable)일까?

profile
오늘은 어떤 새로운 것이 나를 즐겁게 할까?

0개의 댓글