자바에서 문자열을 다루다 보면 String만 쓰다가 어느 순간 "StringBuffer나 StringBuilder를 쓰라"는 말을 듣게 된다. 셋 다 문자열을 다루는데 왜 굳이 구분해서 쓸까? 이번 글에서 그 이유를 제대로 파헤쳐 보자.

핵심부터 말하면, String은 한 번 만들어지면 값을 바꿀 수 없다.
"그럼 str = str + "!" 이렇게 붙이잖아요?"라고 생각할 수 있다. 하지만 이건 기존 문자열을 바꾸는 게 아니라, 새로운 String 객체를 만들어서 변수에 다시 연결하는 것이다.
String str = "Hello";
str = str + " World"; // "Hello"는 그대로, 새 객체 "Hello World"가 생성된다
마치 화이트보드에 쓴 글씨를 지우고 다시 쓰는 게 아니라, 새 화이트보드를 꺼내서 쓰는 것과 같다. 기존 화이트보드("Hello")는 버려지게 된다.
단순히 몇 번 합치는 건 괜찮다. 문제는 반복문에서 문자열을 계속 이어 붙일 때다.
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 매번 새 String 객체가 생성된다
}
이 코드는 반복할 때마다 새 객체를 만들고 이전 객체는 버린다. 10,000번 반복하면 10,000개의 String 객체가 생겼다가 사라진다. 메모리 낭비와 성능 저하로 이어진다.
이 문제를 해결하기 위해 등장한 것이 StringBuffer와 StringBuilder다.
StringBuffer와 StringBuilder는 내부에 수정 가능한 버퍼(buffer) 를 가지고 있다. 문자열을 추가하거나 수정해도 새 객체를 만들지 않고 기존 버퍼를 그대로 수정한다.
화이트보드 비유로 다시 설명하면, 화이트보드에 쓰고 지우고를 반복하는 것이다. 새 화이트보드를 꺼낼 필요가 없다.
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 새 객체 생성 없이 기존 버퍼에 추가
sb.append("!");
System.out.println(sb.toString()); // Hello World!

둘의 사용법은 거의 동일하다. 차이는 딱 하나, 스레드 안전성(Thread Safety) 이다.
| 구분 | StringBuffer | StringBuilder |
|---|---|---|
| 스레드 안전 | O (synchronized) | X |
| 성능 | 상대적으로 느림 | 상대적으로 빠름 |
| 사용 상황 | 멀티스레드 환경 | 단일스레드 환경 |
스레드(Thread)란?
프로그램이 동시에 여러 작업을 처리하는 단위다. 여러 스레드가 동시에 같은 데이터에 접근하면 충돌이 생길 수 있는데,StringBuffer는 이를 방지하는 잠금(synchronized) 처리가 되어 있다.
쉽게 말하면, StringBuffer는 여러 사람이 동시에 접근해도 안전한 공용 메모장, StringBuilder는 혼자만 쓰는 개인 메모장이다.
일반적인 상황(단일 스레드)에서는 StringBuilder를 쓰는 것이 성능상 유리하다.
StringBuffer와 StringBuilder는 동일한 메서드를 제공한다.
StringBuilder sb = new StringBuilder("Hello");
// append(): 문자열 추가
sb.append(" World"); // "Hello World"
// insert(): 특정 위치에 삽입
sb.insert(5, ","); // "Hello, World"
// delete(): 특정 범위 삭제
sb.delete(5, 6); // "Hello World"
// replace(): 특정 범위 교체
sb.replace(6, 11, "Java"); // "Hello Java"
// reverse(): 문자열 뒤집기
sb.reverse(); // "avaJ olleH"
// toString(): String으로 변환 (최종 결과를 꺼낼 때)
String result = sb.toString();
문자열 변경이 거의 없다 → String
단일 스레드에서 문자열을 자주 변경 → StringBuilder (대부분의 경우)
멀티 스레드 환경에서 문자열 변경 → StringBuffer
// 반복문에서 문자열 조합 → StringBuilder 사용
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
sb.append(i).append(", ");
}
System.out.println(sb.toString()); // 1, 2, 3, 4, 5,
String은 불변이라 변경할 때마다 새 객체가 생성된다. 반복적으로 문자열을 변경하는 상황에서는 StringBuilder(단일 스레드) 또는 StringBuffer(멀티 스레드)를 사용하는 것이 올바른 선택이다.
문자열을 "만들어가는" 과정에는 StringBuilder, 다 만들어진 문자열을 "쓰는" 곳에는 String. 이 기준만 기억해도 충분하다.