![[Java] String, StringBuilder, StringBuffer 비교](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1elsw%2FbtsMGqi4fuE%2FHUql8nGuGwBOPWK1qUFWu1%2Fimg.png)
이번 시간에는 자바에서 문자열을 다루는 클래스인 String과 StringBuilder, StringBuffer에 대해서 각각 정리하고 차이를 비교해보려 합니다.
1️⃣ String
String 객체의 주요 특징으로는 불변(immutable)이라는 점입니다.
이 말은 final 키워드처럼 한번 생성되면 변경이 불가능하다는걸 말해요.
public class Main {
public static void main(String[] args) {
String str1 = "Hello";
str1 = str1 + " Beemo"; // 기존 str1을 수정하는 것처럼 보이지만 새로운 객체가 생성됨
}
}
위 코드는 기존 String객체에 문자열을 추가하여 수정하는 코드입니다.
이 코드에서 str1이 "Hello Beemo"로 변경된 것처럼 보이지만, 실제로는 다음과 같은 과정이 일어납니다.
- 기존 "Hello" 객체는 변경되지 않음 (불변성 유지)
- "Hello Beemo"라는 새로운 String 객체가 힙 메모리에 생성
- str1이 새로운 객체 "Hello Beemo"를 가리키도록 재할당
- 기존 "Hello" 문자열은 더 이상 str1이 참조하지 않으므로 GC(가비지 컬렉터)에 의해 정리될 가능성 있음
이처럼 문자열 객체를 단순하게 이어 붙이게 될 경우 메모리의 누수 문제로 이어질 수 있습니다.
이러한 불변 특성으로인해 Thread-safe 하다는 장점을 가져갈 순 있습니다.
2️⃣ StringBuilder
StringBuilder 객체는 String 객체와 달리 가변(mutable) 특성을 가집니다.
이러한 특성으로 인해 직접적으로 값을 수정할 수 있어요.
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
sb.append(" Beemo"); // 기존 객체에서 문자열 변경
}
}
기존 String 객체를 활용한 경우에는 "Hello"라는 객체가 수정되지않고, 새로운 객체가 생성되지만 StringBuilder의 경우 기존 객체가 수정됩니다.
그렇기에 메모리 누수가 없고, 문자열 연산이 빠르다는 장점을 가집니다. (새로운 객체 생성 과정이 없기에)
또한 StringBuilder는 비동기라는 특성을 가집니다.
3️⃣ StringBuffer
StringBuffer 객체 또한 StringBuilder와 동일하게 가변 특성을 가지며 같은 장점을 공유합니다.
두 클래스는 동일한 동작을 가짐에도 불구하고 별도로 존재하는 이유는 StringBuffer는 동기 특성을 가지기 때문입니다.
그렇기에 StringBuffer는 Thread-safe한 장점을 추가로 가지게됩니다.
멀티쓰레드 환경 여부에 따라 두 클래스의 사용이 달리질 수 있으며, 이와 관련하여 세부적인 내용을 더 정리해보도록 하겠습니다.
❓ Thread-safe
Thread-safe란 멀티스레드 환경에서 여러 스레드가 동시에 동일한 리소스(예:문자열)를 다룰 때, 데이터의 일관성을 유지하고 경쟁 조건을 방지하는 특성을 의미합니다.
이러한 특성을 위한 방법으로는 주로 동기화(Synchronization) 기법이 사용됩니다.
✅ StringBuilder vs StringBuffer 소스코드
// StringBuilder vs StringBuffer Thread-safe 테스트
public class Main {
public static void main(String[] args) throws InterruptedException {
// StringBuffer와 StringBuilder 인스턴스 생성
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
// 스레드 생성
Thread bufferThread1 = new Thread(new BufferTask(stringBuffer));
Thread bufferThread2 = new Thread(new BufferTask(stringBuffer));
Thread builderThread1 = new Thread(new BuilderTask(stringBuilder));
Thread builderThread2 = new Thread(new BuilderTask(stringBuilder));
// 스레드 실행
bufferThread1.start();
bufferThread2.start();
builderThread1.start();
builderThread2.start();
// 모든 스레드가 종료될 때까지 대기
bufferThread1.join();
bufferThread2.join();
builderThread1.join();
builderThread2.join();
// 결과 출력
System.out.println("StringBuffer 결과: " + stringBuffer.length()); // 예상: 20000
System.out.println("StringBuilder 결과: " + stringBuilder.length()); // 예상보다 작을 수 있음
}
// StringBuffer 작업을 수행하는 Runnable 구현
static class BufferTask implements Runnable {
private StringBuffer stringBuffer;
public BufferTask(StringBuffer stringBuffer) {
this.stringBuffer = stringBuffer;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
stringBuffer.append("a");
}
}
}
// StringBuilder 작업을 수행하는 Runnable 구현
static class BuilderTask implements Runnable {
private StringBuilder stringBuilder;
public BuilderTask(StringBuilder stringBuilder) {
this.stringBuilder = stringBuilder;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
stringBuilder.append("a");
}
}
}
}
위 코드는 StringBuffer, StringBuilder 각각에 "a"라는 문자열을 10,000번 추가하는 로직을 가지는 쓰레드를 수행한 후에 결과값을 출력하는 코드입니다.

결과를 살펴보면 StringBuffer의 경우 20,000으로 동일한 결과가 나오지만,
StringBuilder는 매번 다른 결과가 나온다는걸 확인할 수 있습니다.
이처럼 StringBuilder는 멀티쓰레드 환경에서 비동기라는 특성으로 인해 안전하지 않다는 단점을 가지지만, 동기 작업이 없기에 속도가 빠르다는 장점을 가지게 됩니다.
저의 경우에 알고리즘을 풀이할 때는 단일 쓰레드 환경이므로 속도가 더 빠른 StringBuilder를 사용하는 편입니다.
✅ 속도 비교 코드
// StringBuilder vs StringBuffer 속도 비교
public class Main {
public static void main(String[] args) {
// 비교할 반복 횟수
final int iterations = 100000;
// StringBuffer 성능 측정
long startTimeBuffer = System.nanoTime();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < iterations; i++) {
stringBuffer.append("a");
}
long endTimeBuffer = System.nanoTime();
long durationBuffer = endTimeBuffer - startTimeBuffer;
System.out.println("StringBuffer time: " + String.format("%,d", durationBuffer) + " nanoseconds");
// StringBuilder 성능 측정
long startTimeBuilder = System.nanoTime();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < iterations; i++) {
stringBuilder.append("a");
}
long endTimeBuilder = System.nanoTime();
long durationBuilder = endTimeBuilder - startTimeBuilder;
System.out.println("StringBuilder time: " + String.format("%,d", durationBuilder) + " nanoseconds");
}
}
위 코드는 문자열 연산을 100,000회 반복하는 단순 로직으로 두 클래스의 속도 차이를 비교하기 위한 코드입니다.
실행을 해보면 아래와 같이 속도에서 큰 차이가 난다는것을 확인할 수 있습니다.

✅ 정리
이번 시간에는 자바에서 문자열을 다루는 클래스인 String, StringBuilder, StringBuffer 클래스에 대해 알아보았습니다.
속도 차이가 어느정도 발생한다는 사실은 인지를 하고 있었는데 이렇게 큰 차이가 발생하는지는 몰랐네요...
정리해보면 두 클래스의 선택은 Thread-safe, 속도 두 가지 관점에서 선택하면 될 것 같습니다.
자바 백엔드는 기본적으로 멀티 쓰레드 환경이기에 StringBuilder의 사용을 최대한 자제하는 편이 좋아보이네요 :)
'Language > Java' 카테고리의 다른 글
[Java] 부동소수점 오차를 피하자! (0) | 2025.03.11 |
---|---|
자바 표준 라이브러리 (지속 업데이트) (0) | 2024.11.28 |
[Java] 객체 동등 비교 - equlas() (0) | 2024.10.05 |
[Java] 인스턴스 변수를 사용하지 않는 메서드는 static을. (0) | 2024.09.07 |
[Java] ArrayList.java (add 메서드 내부 동작) (0) | 2024.03.19 |
개발 기술 블로그, Dev
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!