Java: String vs StringBuilder vs StringBuffer 비교
1. 개요
자바에서 문자열을 다룰 때 흔히 사용하는 세 가지 클래스인 String, StringBuilder, StringBuffer는 모두 CharSequence 인터페이스를 구현하지만, 불변성, 동기화, 성능 측면에서 명확한 차이를 보인다.
- String은 문자열 변경 시마다 새로운 객체를 생성 → 여러 객체가 Heap에 누적
- StringBuilder와 StringBuffer는 동일 객체 내에서 내부 버퍼를 수정 → Heap 메모리를 효율적으로 사용
- StringBuffer는 동기화를 위한 추가 비용으로 인해 메모리뿐 아니라 CPU 리소스도 더 소모됨
이 문서에서는 다음과 같은 관점에서 세 클래스의 차이점을 심층 분석한다.
- 변경 가능 여부 (immutable vs mutable)
- 성능 및 메모리 구조
- 스레드 안전성 및 동기화 여부
- 사용 예시 및 실제 적용 시 고려사항
2. String
2.1 불변 객체(Immutable)의 특징
String
객체는 **불변(immutable)**이다.- 문자열이 변경되는 것처럼 보이지만 실제로는 새로운 객체가 생성된다.
String str = "hello";
str += " world";
// 실제로는 "hello", "hello world" 두 개의 객체가 Heap에 존재
이유
- 내부적으로
char[]
배열을 final로 가지고 있으며, 변경이 불가능 final class String
→ 상속 불가- 안전성, 보안, 캐싱, 문자열 상수 풀(String Pool) 최적화에 유리
2.2 성능 및 메모리 측면
- 연결이 많을수록 비효율적: 반복적인 문자열 조작 시 매번 새로운 객체 생성
- Garbage Collector 부하 증가
2.3 사용 예시
- 단순 문자열 선언 및 불변 문자열 상수
- 파라미터 값, 로그인 ID, 파일 경로 등 변경되지 않는 문자열
3. StringBuilder
3.1 가변 객체(Mutable) + 비동기 환경에 적합
- 내부에
char[]
버퍼를 가지고 있으며, 동기화를 고려하지 않음 - 성능과 메모리 측면에서 매우 효율적
StringBuilder sb = new StringBuilder("hello");
sb.append(" world");
System.out.println(sb.toString()); // hello world
3.2 주요 메서드
sb.append("a");
sb.insert(2, "x");
sb.replace(1, 3, "yz");
sb.delete(0, 2);
sb.reverse();
3.3 성능 테스트 예시
public class BuilderTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100_000; i++) {
sb.append("abc");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder 소요 시간: " + (end - start));
}
}
3.4 사용 시점
- 단일 스레드 환경에서 반복적인 문자열 조작
- 파일 생성, 로깅 등 실시간 데이터 처리
4. StringBuffer
4.1 가변 객체(Mutable) + 스레드 안전(Thread-safe)
StringBuilder
와 거의 동일하나, 모든 메서드가 synchronized 처리- 멀티스레드 환경에서 안정성 보장
StringBuffer sb = new StringBuffer("safe");
sb.append(" thread");
System.out.println(sb); // safe thread
4.2 내부 구조
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
4.3 성능 비교
- 동기화 오버헤드로 인해 성능은
StringBuilder
보다 낮음 - 병렬 처리나 스레드풀 환경에서 안정성을 확보해야 할 때 적합
4.4 멀티스레드 예시
public class BufferThreadTest {
static StringBuffer sb = new StringBuffer();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
sb.append("a");
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("최종 길이: " + sb.length()); // 2000 보장됨
}
}
5. 성능 및 메모리 비교 요약
항목 | String | StringBuilder | StringBuffer |
---|---|---|---|
변경 가능 여부 | ❌ 불변 | ✅ 가변 | ✅ 가변 |
동기화 지원 | ❌ 없음 | ❌ 없음 | ✅ |
성능 | 느림 (매번 객체 생성) | 빠름 | 중간 (동기화 오버헤드 존재) |
메모리 효율성 | 낮음 | 높음 | 중간 |
적합한 환경 | 상수, 설정값, 로그 ID 등 | 단일 스레드 문자열 조작 | 멀티스레드 문자열 조작 |
6. 결론 및 실무 팁
- ✅ String: 변경이 없는 정적 문자열 (상수, config 값 등)
- ✅ StringBuilder: 반복문 내에서 문자열 누적, 대량 처리 시
- ✅ StringBuffer: 동기화가 필요한 멀티스레드 환경에서만 사용
✅ StringBuilder
vs StringBuffer
– 언제 뭐 써야 해?
항목 | StringBuilder | StringBuffer |
---|---|---|
스레드 안전성 | ❌ 비동기(스레드 안전 아님) | ✅ 동기화(synchronized, 스레드 안전) |
속도 | 빠름 | 상대적으로 느림 |
코딩 테스트/일반 개발 | ✅ 단일 스레드 환경에서 최적 | ❌ 보통 코테에선 불필요한 오버헤드 발생 |
멀티스레드 환경 | ❌ 사용 시 동기화 문제 생길 수 있음 | ✅ 멀티스레드 환경에서 안전하게 사용 가능 |
사용 예 | 코딩테스트, 파일 파싱, 로그 누적, 텍스트 생성 | 서버에서 공유 자원 처리, 멀티스레드 로그 등 |
- 코딩 테스트, 단일 스레드 로직:
StringBuilder
사용 (기본값) - 멀티스레드 환경 (예: 서버 개발):
StringBuffer
사용
💡 JDK 1.5 이상에서는 StringBuilder
를 기본으로 고려하고, 동기화가 꼭 필요한 경우에만 StringBuffer
를 선택하자.