[Java] String 성능 문제

이상현·2025년 11월 27일

Java

목록 보기
25/25
post-thumbnail

String 클래스는 잘 사용하면 상관이 없지만 잘못 사용하면 메모리에 많은 영향을 준다.

왜 그럴까?
String은 불변(Immutable) 속성을 가진다. 즉, 문자열을 수정하는 것처럼 보여도 실제로는 수정되는 것이 아니라 매번 새로운 객체를 생성해서 참조를 바꾸는 방식이다. 반면 아래 소개할 두 클래스는 가변(Mutable) 속성을 지녀 내부 버퍼를 직접 수정하므로 메모리 낭비가 적다.

StringBuffer 클래스와 StringBuilder 클래스

문자열을 만드는 클래스는 String, StringBuffer, StringBuilder 가 많이 사용된다. (모두 CharSequence 인터페이스의 구현체이다.)

StringBuffer 와 StringBuilder 는 사용하는 메소드는 동일하다. 둘의 차이는 스레드에 안전한지의 차이이다.

StringBuffer 는 스레드에 안전하게 설계되어있어 여러개의 스레드에서 하나의 StringBuffer 객체를 처리해도 문제가 발생하지 않는다. 하지만 StringBuilder 는 단일 스레드에서만 안정성을 보장한다.

String vs StringBuffer vs StringBuilder

이 세가지의 실제 성능을 코드로 비교해보겠다. (jdk 17)

코드

public static void main(String[] args) {
    int n = 100_000;

    long t0 = System.nanoTime();
    String s = "";
    for (int i = 0; i < n; i++) {
        s += i;
    }
    long t1 = System.nanoTime();

    StringBuilder sb = new StringBuilder(n * 2);
    for (int i = 0; i < n; i++) {
        sb.append(i);
    }
    long t2 = System.nanoTime();

    StringBuffer sbf = new StringBuffer(n * 2);
    for (int i = 0; i < n; i++) {
        sbf.append(i);
    }
    long t3 = System.nanoTime();

    System.out.printf("String       : %4d ms (len=%d)%n", (t1 - t0) / 1_000_000, s.length());
    System.out.printf("StringBuilder: %4d ms (len=%d)%n", (t2 - t1) / 1_000_000, sb.length());
    System.out.printf("StringBuffer : %4d ms (len=%d)%n", (t3 - t2) / 1_000_000, sbf.length());
}

위 코드는 같은 작업(숫자를 문자열로 이어붙이기)을 10만 번 수행하며 System.nanoTime()으로 String/Builder/Buffer의 시간을 측정하는 코드이다.

결과

String       :  781 ms (len=488890)
StringBuilder:    2 ms (len=488890)
StringBuffer :    2 ms (len=488890)
  • String: 매번 새 객체를 만들어 평균 수백 ms대. 반복이 커질수록 매우 느려진다.
  • StringBuilder: 동기화가 없어 단일 스레드에서 가장 빠르다(수 ms 수준). 따라서 알고리즘 문제에서 가장 많이 쓰인다.
  • StringBuffer: Builder 비슷하게 나왔다. 실제 웹 서버를 구현한다면 멀티쓰레드 안정성을 고려해서 이 객체를 사용하자.

특징 정리

특성StringStringBuilderStringBuffer
가변성불변 (Immutable)가변 (Mutable)가변 (Mutable)
스레드 안전안전 (불변이므로)불안전 (Not Synchronized)안전 (Synchronized)
주요 용도단순 참조 및 조회단일 스레드 루프 연산멀티 스레드 환경
성능연산 시 느림가장 빠름빠름 (동기화 오버헤드 있음)

결론

반복적인 문자열 결합은 단일 스레드는 StringBuilder, 멀티 스레드는 StringBuffer를 사용하는게 좋다.
for 루프 안 +=를 빌더로 바꾸는 것만으로도 수십~수백 배 개선된다.
new StringBuilder(예상길이)처럼 초기 용량을 잡아두면 리사이즈 비용을 줄여 추가로 빨라진다.
습관적으로 빌더 패턴을 쓰면 불필요한 객체 생성과 GC 부담을 크게 줄일 수 있다.

0개의 댓글