Java String 합치기 - Java String habchigi

서론

개발을 하다가 멘토님께 알게 된 사실을 정리한다.

사실 나는 문자열 붙이기를 할 때, '+' 를 활용하면 메모리 낭비가 너무 커질 수 있어 지양해야한다는 생각을 가지고 코드를 작성해왔다.

이 주제는 상당히 오래전부터 자바를 공부하는 우리 컴공생들에게 (면접질문 등으로) 자주 논의되던 이야기였다.

문자열 붙이기에 '+' 를 이용하지말아야한다는 근거

String 객체는 불변 객체이다. 즉 한번 객체가 생성되면 그 객체 내용을 변경하는건 불가능하다.

그래서 우리가 한 문자열을 다른 문자열과 합치게 되면 기존 문자열의 내용이 변하는게 아니라 새로운 문자열이 생성된다.

그리고 이제 참조되지않는 (아래 그림의 경우 "Hello") 문자열은 더이상 어느 코드에서도 참조되지않으므로 GC(Garbage Collector)에 보내져 소멸을 기다린다.

Java String 합치기 - Java String habchigi
힙 영역을 차지하고 있는 문자열의 주소를 스택의 str 이 가리킨다.

만약 문자열을 백만개 합친다면?

문자열을 백만개 합친다면 쓸모없는 문자열이 100만 - 1 개 만큼 생겨나게 될 것이고 이는 컴퓨터 자원의 당연히 큰 낭비다.

그래서 StringBuilder 를 써야 한다

StringBuilder 는 가변적인 문자열 객체처럼 동작하고, append method 는 문자열 붙이기(concatenation)를 할때마다 문자열이 새로 생겨나는걸 막아준다.

public static void main(String [] args) {
    // '+'
    String str = "Hello " + "World";
    // StringBuilder
    StringBuilder sb = new StringBuilder();
    sb.append("Hello ");
    sb.append("World");

}

그래서 나는 당연히도, ㅈ간단한 문자열 합치기에도 StringBuilder 를 이용해서 해왔다.

그러나 그러지 않아도 된다

하지만 JDK1.5 출시 이후부터 (출시일은 2004년 9월 4일이다...)
자바 컴파일러는 간단한 형식의 '+' 를 이용한 문자열 합치기를 자동적으로 StringBuilder 를 활용한 문자열 붙이기로 변환하기 때문에 '+' 를 사용해도 성능 상의 이슈가 발생하지 않는다!

결론

*코드 가독성을 위해 그냥 '+' 로 문자열을 합치자. 단 loop 내 문자열 합치기는 예외 *

그러나 예외도 존재한다. 만약 loop 안에서 문자열을 합쳐야하는 경우에는 java compiler 가 StringBuilder 로 변환하지 않는다.
즉 이런 경우에는 개발자가 명시적으로 StringBuilder 를 사용해주는 것이 좋다.

나는 이 사실을 모르고, 2004년도부터 개선되어온 부분에 대해서 알지 못한채 '+' 를 사용한 문자열 붙이기를 죄악시했다.
오히려 기능상의 차이가 없을 때에도 StringBuilder 를 사용해 문자열을 붙이게 되면 오히려 코드가 길어져 가독성이 떨어지지 않을까 생각한다.
큰 걸 하나 배운 것 같다.

https://dzone.com/articles/string-concatenation-performacne-improvement-in-ja

서론

자바에서 '+' 연산을 통한 문자열 합치기를 지양하라는 흥미로운 주제를 보게 되어 글을 작성하게 되었습니다.
그전에 먼저 String과 StringBuffer, 그리고 StringBuilder에 대한 사전 지식이 있으면 이해하기 수월합니다. 해당 내용은 아래 게시글에서 정리한 적이 있습니다.

[Java] StringBuffer와 StringBuilder

서론 [Java] String 클래스 서론 c언어 같은 경우 문자열을 char형의 배열로 다루었지만 자바에서는 문자열을 위한 클래스를 제공합니다. 그것이 바로 String 클래스인데, String 클래스는 문자열을 저장

dkswnkk.tistory.com

Java String 합치기 - Java String habchigi

[Java] String 클래스

서론 c언어 같은 경우 문자열을 char형의 배열로 다루었지만 자바에서는 문자열을 위한 클래스를 제공합니다. 그것이 바로 String 클래스인데, String 클래스는 문자열을 저장하고 이를 다루는데 필

dkswnkk.tistory.com

Java String 합치기 - Java String habchigi

간단하게 다시 요약하면 아래와 같습니다.

1. String 클래스는 변경 불가능한 immutable 클래스이다.

  • 한번 생성된 String 인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경할 수는 없다. 예를 들어 아래의 코드와 같이 '+'연산자를 이용해서 문자열을 결합하는 경우 인스턴스 내의 문자열이 바뀌는 것이 아니라 새로운 문자열("ab")이 담긴 String 인스턴스가 생성된다.

2. String과 달리 StringBuffer는 내용을 변경할 수 있다.

  • StringBuffer 클래스의 인스턴스를 생성할 때, 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집하기 위한 공간(buffer)으로 사용된다.
  • 만약 버퍼의 크기가 작업하려는 문자열의 길이보다 작을 때는 내부적으로 버퍼의 크기를 증가시키는 작업이 수행되며, 배열의 길이는 변경될 수 없으므로 새로운 길이의 배열을 생성한 후에 이전 배열의 값을 복사하는 방법으로 수행된다.

그렇다면 우리는 왜 자바에서 '+' 연산을 통한 문자열 합치기를 지양해야 하는지 한번 알아보겠습니다.

String '+' 연산이 일어나는 과정

Java String 합치기 - Java String habchigi
stackoverflow 참조

Java에서 Stirng '+' 연산자는 Java 컴파일러에서 구현되며 컴파일 타임에 컴파일 전 내부적으로 StringBuilder 클래스를 만든 후 다시 문자열로 반환합니다. 즉 아래의 코드처럼 동작합니다.

public class Test {
    public static void main(String[] args) {
        String banana = "바나나";
        banana += "쥬스";
        // 위 아래 동일
        String bananaJuice = new StringBuilder("바나나").append("쥬스").toString();
    }
}

벌써부터 왜 사용하면 안되는지 감을 잡으신 분도 있으시겠지만 조금 더 확실히 비교를 해 봅시다.
만약 위 코드처럼 단순한 '+' 연산이 반복문 안에 들어가면 어떻게 될까요?

public class Test {
    public static void main(String[] args) {
        String number = "0";
        for(int i=1; i<=10000; i++){
            number += "i";
        }
        // 위 아래 동일
        String number2 = "0";
        for(int i=1; i<=10000; i++){
            number2 = new StringBuilder(number2).append(i).toString();
        }
    }
}

위 코드처럼 반복문 안에서 '+' 연산을 수행한다면 반복문의 횟수만큼 StringBuilder 객체가 생성되고 append()와 toString() 메서도의 호출이 매번 발생하게 됩니다.
따라서 성능이 저하될 수밖에 없고, 메모리도 낭비도 커지게 됩니다.

public class Test {
    public static void main(String[] args) {
        final StringBuilder a = new StringBuilder();

        for (int i = 0; i < 10000; i++) {
            a.append(i);
        }

        final String b = a.toString();
    }
}

그렇기에 위와 같이 더하기 연산이 많이 일어나는 경우 처음부터 StringBuilder 클래스를 사용하여 문자열을 합치는 게 더 좋은 방법입니다.

StringBuilder vs String 성능 측정

public class Test {
    public static void main(String[] args) {
        stringBuilderTest();
        stringTest();
    }

    public static void stringTest(){

        String result = "";
        long start = System.currentTimeMillis();

        for(int i = 0 ; i < 100000; i++){
            result += "test";
        }
        long end = System.currentTimeMillis();

        System.out.println("String exec time : " + (end - start));
    }

    public static void stringBuilderTest(){

        StringBuilder result = new StringBuilder();
        long start = System.currentTimeMillis();

        for(int i = 0 ; i < 100000; i++){
            result.append("test");
        }
        long end = System.currentTimeMillis();

        System.out.println("String builder exec time : " + (end - start));

    }
}

한번 직접 코드를 작성하여 처음부터 StringBuilder를 사용한 경우와 String '+' 연산의 수행 속도를 비교해 보았습니다.

Java String 합치기 - Java String habchigi
실행 결과

위와 같이 String '+' 연산을 사용하면 StringBuilder로 바꾸는 과정이 일어나기 때문에 반복문의 횟수만큼 StringBuilder 객체가 만들어지고 '+' 연산 내부적으로 많은 일들이 일어나서 예상대로 시간이 많이 걸리는 것을 볼 수 있습니다.
따라서 결론적으로 많은 문자열 합치기 연산이 필요하다면 StringBuilder를 사용하는 것이 성능면에서 좋다는 것을 알 수 있습니다.

참고

GitHub - wjdrbs96/Today-I-Learn: Today I Learned. 그날 그날 모든 활동들을 정리

:octocat: Today I Learned. 그날 그날 모든 활동들을 정리. Contribute to wjdrbs96/Today-I-Learn development by creating an account on GitHub.

github.com

Java String 합치기 - Java String habchigi

자바에서 String 다룰 때 오해와 진실

2018년 5월 18일 from blog.hazard.kr 어자피 다들 먹고사는데 자바 안다뤄본 사람 있나? 어? 자바 안해봤어? 넌 그럼 축복받은 거야. 축하한다. 어쨌든,...

dev.to

Java String 합치기 - Java String habchigi