본문 바로가기
Study/Java

[Java] String + 연산 원리와 성능 비교

by DevJaewoo 2023. 2. 8.
반응형

개발하던 중 갑자기 String의 + 연산이 어떻게 되는지 궁금해져 찾아봤다.

String + 연산 동작원리, StringBuilder, StringBuffer와의 성능 차이에 대해 알아보자.


String + 연산 동작원리

String의 +연산은 컴파일 타임에 new StringBuilder.append.toString으로 변환된다.

따라서 아래의 두 코드는 동일하다.

String s1 = "";
for(int i=1; i<=loop; i++) {
    s1 += i;
}

String s2 = "";
for(int i=1; i<=loop; i++) {
    s2 = new StringBuilder(s2).append(i).toString();
}

 

코드를 보면 문자열을 합치기 위해 매번 StringBuilder 인스턴스를 만들고, 다시 String으로 변환하는 과정을 거치기 때문에 비효율적이라는 것을 알 수 있다.


StringBuilder와 StringBuffer

문자열을 추가하는 방법엔 + 이외에 StringBuilder 이외에도 StringBuffer를 사용하는 방법이 있다.

둘의 차이는 synchronized 여부이다. append 이외에도 StringBuffer 클래스의 모든 함수에는 전부 synchronized가 붙어있다.

때문에 StringBuffer는 StringBuilder에 비해 성능이 조금 떨어지지만, 멀티쓰레드 환경에서 더 안정적이라는 특징이 있다.

// StringBuilder
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

// StringBuffer
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}

성능 측정

문자열을 합치는 방법들 간에 성능이 얼마나 차이날지 한번 측정해보자.

측정한 항목은 다음과 같다.

  1. String + 연산
  2. StringBuilder (반복마다 선언)
  3. StringBuilder (초기 1회 선언)
  4. StringBuffer
public class Main {
    public static void main(String[] args) {
        int loop = 200000;
        
        // Operator +
        String s1 = "";
        long t1 = System.currentTimeMillis();
        for(int i=1; i<=loop; i++) {
            s1 += i;
        }
        System.out.println("operator +: " + (System.currentTimeMillis() - t1) + "ms");
        
        // StringBuilder (반복마다 생성)
        String s2 = "";
        long t2 = System.currentTimeMillis();
        for(int i=1; i<=loop; i++) {
            s2 = new StringBuilder(s2).append(i).toString();
        }
        System.out.println("StringBuilder(new): " + (System.currentTimeMillis() - t2) + "ms");
        
        // StringBuilder (초기 1회 생성)
        StringBuilder stringBuilder = new StringBuilder();
        long t3 = System.currentTimeMillis();
        for(int i=1; i<=loop; i++) {
            stringBuilder.append(i);
        }
        String s3 = stringBuilder.toString();
        System.out.println("StringBuilder(once): " + (System.currentTimeMillis() - t3) + "ms");
        
        // StringBuffer
        StringBuffer stringBuffer = new StringBuffer();
        long t4 = System.currentTimeMillis();
        for(int i=1; i<=loop; i++) {
            stringBuffer.append(i);
        }
        String s4 = stringBuffer.toString();
        System.out.println("StringBuffer: " + (System.currentTimeMillis() - t4) + "ms");
    }
}

 

측정 결과는 아래와 같다.

+ 연산과 매번 StringBuilder를 생성한 방식의 소모 시간은 거의 차이가 없고,

StringBuilder(최초 1회 선언)와 StringBuffer를 사용했을 때에 비해 훨씬 많은 시간이 걸리는 것을 알 수 있다.

매번 StringBuilder를 생성하고, 결과를 매번 변수에 대입해서 이런 차이가 나온것 같다.

operator +: 21884ms
StringBuilder(new): 21313ms
StringBuilder(once): 18ms
StringBuffer: 17ms

 

 

StringBuilder와 StringBuffer의 시간이 비슷해 둘만 따로 1000만번 돌려봤다.

StringBuffer가 약간 더 느린 것을 볼 수 있다.

StringBuilder(once): 379ms
StringBuffer: 497ms

 

문자열을 한두번 더하는거면 + 연산을 사용해도 큰 차이 없겠지만, 여러번 더해야 할 경우 StringBuilder나 멀티스레드 환경일 경우 StringBuffer를 미리 선언하여 사용하는게 좋을 것 같다.


[ 관련 글 ]

 

[Java] String Pool 저장 조건

Intro Java는 메모리 공간을 절약하기 위해 Heap 영역에 String Pool 영역을 만들고, 이 영역에 Constant 문자열을 저장한다. 그런데 이 String Pool 영역에 들어가는 조건이 애매하-다. 예를 들어, 아래의 s2, s

devjaewoo.tistory.com


참고자료

반응형

'Study > Java' 카테고리의 다른 글

[Java] String Pool 저장 조건  (0) 2023.02.08