Algorithm

[Java] 문자열 + 연산과 StringBuilder 비교

kimyoungrok 2024. 11. 9. 02:04

 

Java에서 문자열 결합 시 많이 사용하는 + 연산은 직관적이고 간단하지만, 성능 측면에서 적절하지 않은 경우가 있습니다. 이번 글에서는 문자열 결합의 효율적인 방법과 이유에 대해 설명하겠습니다.

Java에서의 + 연산

Java의 String은 immutable(불변)한 객체입니다. 따라서 String 간의 + 연산은 기존 객체를 수정하지 않고, 새로운 String 객체를 생성하게 됩니다. 이는 다음과 같은 코드에서 성능 문제가 발생할 수 있습니다.

// 비효율적인 코드 예시
String res = "";
for (int i = 0; i < 100; ++i) {
    res += i; // 매번 새로운 String 객체 생성
}

 

위 코드는 for 문이 실행될 때마다 새로운 String 객체를 생성하여 성능 저하를 유발합니다.

 

 

+ 연산이 효율적인 경우

단일 라인의 +연산에서는 Java 컴파일러가 내부적으로 최적화합니다.

String res = "Hello" + " " + "World" + "!";

컴파일러가 위 코드를 다음과 같이 변환할 수 있습니다.

String res = new StringBuilder()
                    .append("Hello")
                    .append(" ")
                    .append("World")
                    .append("!")
                    .toString();

즉, 단일 라인의 경우에는 + 연산도 최적화되어 StringBuilder를 사용하는 것과 동일한 효과를 얻을 수 있습니다.

이는 JLS 15.18.1에 명시되어 있습니다.

https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.18.1

 

Chapter 15. Expressions

 

docs.oracle.com

To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.

 

 

반복문에서는 왜 + 연산이 비효율적인가?

단일 라인의 + 연산은 최적화가 되지만, 반복문에서는 결국 매번 새로운 문자열 객체를 생성하기 때문에 비효율적입니다.

따라서, 반복해 문자열을 결합해야 하는 경우 mutable한 문자열 결합 클래스를 사용하는 것이 좋습니다.

 

StringBuilder

StringBuilder는 수정 가능한 가변 문자열 클래스입니다.

객체를 한 번 생성한 후 append() 메서드를 사용해 문자열을 효율적으로 결합할 수 있습니다.

append()는 다양한 객체에 대한 명시적 형 변환을 지원합니다. 단, 내부적으로는 객체의 toString()을 호출해 문자열을 변환합니다.

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i < 10; ++i) {
            sb.append(i).append('\n');
        }
        System.out.print(sb); // 연결한 문자열이 출력된다.
    }
}

 

멀티스레드 환경에서는 StringBuffer

StringBuilder는 Non-Synchronized 클래스입니다. 멀티스레드 환경에서 동기화 문제가 발생할 수 있기 때문에, 이 경우에는 StringBuffer를 사용해 안전하게 문자열 결합을 수행할 수 있습니다.

 

StringJoiner

StringJoiner는 구분자(Delimiter) 기반 문자열 결합 클래스입니다.

구분자를 자동으로 추가해주기 때문에 StringBuilder보다 코드가 더 간결하고 명확해집니다.

add() 는 CharSequence 만을 인자로 받는다.

public class Main {
    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner("\n");
        for (int i = 1; i < 10; ++i) {
            sj.add(String.valueof(i)); // 구분자를 추가하지 않아도 된다.
        }
        System.out.print(sj);
    }
}

다음과 같이 구분자 외에도 접미사/접두사를 지정할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        StringJoiner joiner = new StringJoiner(", ", "[", "]");
        joiner.add("Apple").add("Banana").add("Orange");
        System.out.println(joiner.toString()); // 출력: [Apple, Banana, Orange]
    }
}

 

결론

+ 연산은 단일 라인에서는 효율적이지만, 반복문 등 복잡한 상황에서는 성능 저하의 원인이 됩니다. 이러한 경우에는 StringBuilder나 StringJoiner와 같은 클래스를 사용해 성능을 개선할 수 있습니다.