본문 바로가기
language/java

StringBuilder vs StringBuffer

by 죄니안죄니 2026. 6. 16.
반응형

이번 글은 Java에서 가장 많이 사용하는 클래스 중 하나이지만,
실제로 내부 동작을 정확히 아는 사람은 많지 않다.

면접에서도 자주 나오는 질문이다.

"StringBuilder와 StringBuffer의 차이가 synchronized 말고 또 있나요?"

또는

"String a = a + b는 실제로 어떻게 동작하나요?"

이번 글에서는 이 질문에 답할 수 있도록 내부 원리까지 설명한다.


StringBuilder vs StringBuffer

둘 다 문자열을 변경(Mutable)하기 위한 클래스이며, 가장 큰 차이는 Thread-safe 여부이다.

하지만 이것만 알고 끝내면 절반만 이해한 것이다.


먼저 String부터 이해해야 한다.

String은 immutable(불변 객체)이다.

 
String str = "Hello";

str += " World";
 

많은 사람들이

Hello

↓

Hello World
 

로 문자열이 수정되었다고 생각한다.

하지만 실제로는 아니다.


실제 메모리에서는

 
String str = "Hello";
 
Heap(String Pool)

"Hello"

▲
│
str
 

그리고

 
str += " World";
 

가 실행되면

Heap(String Pool)

"Hello"

"Hello World"

          ▲
          │
         str
 

기존 문자열은 그대로 있고,

새로운 String 객체가 생성된다.


String은 왜 immutable일까?

이유는 매우 많다.

String Pool 재사용

HashMap Key 안정성

Thread-safe

보안
 

등의 이유 때문이다.


하지만 문제가 있다.


반복문에서 String +

 
String result = "";

for (int i = 0; i < 3; i++) {

    result += i;

}
 

많은 사람들이

result

↓

0

↓

01

↓

012
 

한 객체가 계속 수정된다고 생각한다.


실제로는

""

↓

"0"

↓

"01"

↓

"012"
 

객체가 매번 새로 생성된다.


메모리

Heap

""

"0"

"01"

"012"
 

총 4개의 객체가 만들어진다.


1000번 반복하면?

1000개의 String 객체 생성

↓

GC 대상 증가

↓

성능 저하
 

그래서 StringBuilder가 등장했다.

StringBuilder는 immutable이 아니라 mutable이다.


예제

 
StringBuilder sb =
        new StringBuilder();

sb.append("Hello");

sb.append(" World");
 

메모리

StringBuilder

----------------

char[]

H

e

l

l

o

W

o

r

l

d

----------------
 

객체 하나를 계속 수정한다.


새 객체를 만들지 않는다.


append()는 어떻게 동작할까?

 
sb.append("ABC");
 

실제로는

기존 char[]

↓

뒤에 문자 복사

↓

끝
 

이다.


새로운 String을 생성하지 않는다.


내부 구조

StringBuilder 내부는 거의

 
class StringBuilder {

    char[] value;

    int count;

}
 

형태이다.


예를 들어

 
StringBuilder sb =
        new StringBuilder();
 

생성하면

char[]

--------------------

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

capacity = 16

count = 0

--------------------
 

append

 
sb.append("ABC");
 

결과

char[]

--------------------

A B C _ _ _ _ _ _ _ _ _ _ _ _ _

count = 3

--------------------
 

append

 
sb.append("DEF");
 

결과

char[]

--------------------

A B C D E F _ _ _ _ _ _ _ _ _ _

count = 6

--------------------
 

공간이 부족하면?

예를 들어

capacity가

16
 

인데

20글자를 넣으면


새로운 배열을 만든다.

기존

16

↓

새 배열 생성

↓

34

↓

기존 내용 복사

↓

append
 

실제로 JDK는

newCapacity

=

(oldCapacity * 2) + 2
 

방식으로 증가시킨다.


StringBuffer는?

거의 동일하다.

내부 구조도 비슷하다.

char[]

count

append()

insert()
 

모두 동일하다.


단 하나의 차이가 있다.


synchronized

StringBuffer

 
public synchronized
StringBuffer append(String str)
 

StringBuilder

 
public
StringBuilder append(String str)
 

StringBuffer는

모든 메서드가 synchronized이다.


synchronized가 무엇일까?

Thread1

↓

append()

↓

Lock 획득

↓

작업

↓

Lock 해제

↓

Thread2 수행
 

즉,

동시에 두 Thread가

append를 수행하지 못한다.


StringBuilder는

Thread1

↓

append()

Thread2

↓

append()

동시 실행 가능
 

이다.


그래서

Lock 없음

↓

더 빠름
 

이다.


성능 비교

String +

객체 계속 생성

↓

가장 느림
 

StringBuffer

Mutable

+

Lock

↓

중간
 

StringBuilder

Mutable

+

Lock 없음

↓

가장 빠름
 

그런데 + 연산도 빠르던데?

좋은 질문이다.


컴파일러는

 
String result =
        "Hello" + " World";
 

컴파일 시

"Hello World"
 

로 바꾼다.

이를

Constant Folding

이라고 한다.


 
String result =
        a + b;
 

컴파일러가

 
String result =
        new StringBuilder()

                .append(a)

                .append(b)

                .toString();
 

로 변환한다.


즉,

우리가

 
a + b
 

를 작성해도

내부적으로는

StringBuilder

↓

append()

↓

toString()
 

이 수행된다.


그럼 항상 +를 써도 되는가?

아니다.


한 번만 연결

 
String result =
        firstName + lastName;
 

괜찮다.

컴파일러가 최적화한다.


반복문

 
String result = "";

for (...) {

    result += value;

}
 

매 반복마다

StringBuilder 생성

↓

append

↓

toString

↓

새 String 생성
 

이 반복된다.


그래서 반복문에서는

반드시

 
StringBuilder sb =
        new StringBuilder();

for (...) {

    sb.append(value);

}

return sb.toString();
 

를 사용하는 것이 좋다.


실무 기준


일반 문자열

 
String name =
        first + last;
 

그냥 String 사용.


반복문

 
StringBuilder sb =
        new StringBuilder();
 

권장.


멀티스레드 공유 객체

 
StringBuffer
 

가능하지만,


실무에서는 거의

StringBuilder

+

외부 Lock

또는

Concurrent 자료구조
 

를 사용한다.

StringBuffer를 직접 사용하는 경우는 많지 않다.


그림으로 정리

String

append

↓

새 String 생성

↓

기존 객체 유지
 

StringBuilder

append

↓

기존 char[] 수정

↓

객체 그대로
 

StringBuffer

append

↓

Lock 획득

↓

기존 char[] 수정

↓

Lock 해제
 

면접 포인트

StringBuilder와 StringBuffer의 차이

StringBuilderStringBuffer
Mutable Mutable
synchronized 없음 synchronized 있음
Thread-safe 아님 Thread-safe
더 빠름 상대적으로 느림

String + 는 내부적으로 어떻게 동작하는가?

컴파일러가

 
a + b
 

 
new StringBuilder()

.append(a)

.append(b)

.toString()
 

형태로 변환한다.


반복문에서 StringBuilder를 사용하는 이유는?

String은 immutable이므로 +를 반복하면 매번 새로운 객체가 생성된다.

StringBuilder는 하나의 내부 배열(char 배열 또는 최신 JDK에서는 byte 배열 기반 구현)을 재사용하면서 내용을 변경하므로 객체 생성과 GC 부담을 크게 줄일 수 있다.


다음 글 추천

다음은 BigDecimal을 사용하는 이유이다.

이 글에서는

 
System.out.println(0.1 + 0.2);
 

의 결과가

0.30000000000000004
 

가 되는 이유부터,

  • IEEE 754 부동소수점
  • 금융 시스템에서 BigDecimal을 사용하는 이유
  • new BigDecimal("0.1")과 new BigDecimal(0.1)의 차이
  • equals()와 compareTo()의 함정

까지 실무 중심으로 이어서 설명할 수 있다.

반응형

'language > java' 카테고리의 다른 글

ThreadLocal 원리  (0) 2026.06.16
BigDecimal을 사용하는 이유  (0) 2026.06.16
Integer Cache  (0) 2026.06.16
AutoBoxing / Unboxing  (0) 2026.06.16
Serialization / Deserialization  (0) 2026.06.16

댓글