63. 문자열 연결은 느리니 주의하라

문자열 연결 연산자(+)란?

자바에서 + 연산자를 사용하면 두 문자열을 쉽게 합칠 수 있다

문자열 연결은 자바에서 가장 기본적인 연산 중 하나이다

// 문자열 연결 예시
String firstName = "홍";
String lastName = "길동";
String fullName = firstName + lastName;  // "홍길동"

문자열 연결 연산자(+)의 문제점

+ 연산자는 편리하지만 많은 문자열을 연결할 때 심각한 성능 문제가 발생한다

✔️ 문자열 연결 연산자로 문자열 n개를 잇는 시간은 n²에 비례한다

왜 이런 일이 발생할까?

  1. String은 불변(immutable)이다

    • 한번 생성된 문자열은 변경할 수 없다

    • 두 문자열을 연결하면 항상 새로운 String 객체가 생성된다

  2. 매번 새로운 복사본이 만들어진다

    • 기존 내용을 복사하고 새 내용을 추가해야 한다

    • 연결할수록 복사할 내용이 늘어난다

// 잘못된 예 - 문자열 연결 연산자를 반복문에서 사용
String result = "";
for (int i = 0; i < 100000; i++) {
    result += "a";  // ❌ 매우 느림!
}

위 코드의 실행 과정

  1. result += "a" → 새 문자열을 위한 메모리 할당

  2. result의 내용을 새 메모리에 복사

  3. "a"를 복사한 내용 뒤에 추가

  4. 새 문자열 객체 생성

  5. 반복...

StringBuilder로 해결하기

자바는 문자열 연결 성능 문제를 해결하기 위해 StringBuilder라는 클래스를 제공한다

StringBuilder의 특징

  1. 가변(mutable)이다

    • 내용을 변경할 수 있다

    • 새 객체를 만들지 않고 기존 객체에 내용을 추가한다

  2. 내부적으로 크기가 조정되는 배열을 사용한다

    • 필요에 따라 자동으로 용량이 늘어난다

    • 문자열 연결이 선형 시간(n)에 비례한다 (+ 연산자의 제곱 시간보다 훨씬 빠름)

// 좋은 예 - StringBuilder 사용
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append("a");  // ✅ 훨씬 빠르다!
}
String result = sb.toString();

StringBuilder의 주요 메소드

  1. append(): 문자열 끝에 내용 추가

  2. insert(): 지정한 위치에 내용 삽입

  3. delete(): 특정 범위의 내용 삭제

  4. toString(): 완성된 문자열로 변환

성능 비교

문자열 연결 방식에 따른 성능 차이를 살펴보자

// 1. String 연결 연산자(+)
String str = "";
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    str += "a";
}
long end = System.currentTimeMillis();
System.out.println("String +: " + (end - start) + "ms");  // 약 3000ms 이상

// 2. StringBuilder
StringBuilder sb = new StringBuilder();
start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    sb.append("a");
}
String sbResult = sb.toString();
end = System.currentTimeMillis();
System.out.println("StringBuilder: " + (end - start) + "ms");  // 약 5ms

StringBuffer vs StringBuilder

문자열 연결을 위한 클래스로 StringBuffer도 있다. 차이점은 무엇일까?

두 클래스의 차이점

  1. StringBuffer: 스레드 안전(thread-safe)

    • 여러 스레드가 동시에 접근해도 안전하다

    • synchronized 키워드로 동기화되어 있다

    • 멀티스레드 환경에서 사용해야 할 때 적합하다

  2. StringBuilder: 스레드 안전하지 않음

    • 동기화되어 있지 않아 더 빠르다

    • 단일 스레드에서 사용할 때 적합하다

    • 일반적인 상황에서는 StringBuilder를 사용하는 것이 좋다

// StringBuffer 예시 (멀티스레드 환경)
StringBuffer buffer = new StringBuffer();
buffer.append("안녕하세요");
buffer.append(" 반갑습니다");

// StringBuilder 예시 (일반적인 사용)
StringBuilder builder = new StringBuilder();
builder.append("안녕하세요");
builder.append(" 반갑습니다");

결론

  1. 한 번만 연결하거나 적은 양의 문자열을 연결할 때

    • + 연산자 사용해도 괜찮다

  2. 반복문에서 문자열을 연결하거나 많은 양의 문자열을 연결할 때

    • StringBuilder를 사용하자!

    • 성능이 중요하다면 StringBuilder의 초기 용량을 지정하는 것도 좋다

    // 초기 용량 지정
    StringBuilder sb = new StringBuilder(1000);
  3. 멀티스레드 환경에서는 StringBuffer를 고려하자


🧩 어려웠던 점

자바의 String이 불변이라는 개념과 이로 인해 + 연산자가 왜 성능 문제를 일으키는지 이해하는 데 시간이 걸렸다

StringBuilder와 StringBuffer의 차이점과 각각 언제 사용해야 하는지 구분하는 것이 처음에는 헷갈렸다

💡 느낀 점

작은 최적화가 큰 성능 차이를 만들 수 있다는 것을 깨달았다.

Last updated