본문 바로가기

IT/Java

[Java] String, StringBuffer, StringBuilder 란?? 그리고 속도 비교

1. Introduction

 

String + 연산자가 느리다는 것은 널리 알려진 사실이다.

String은 내부적으로 char[]을 사용하는데 이 배열은 변경이 불가능하다.

String은 인스턴스 생성 시에만 char[]에 값을 넣을 수 있어서 아래와 같은 코드에서 String new 연산이 O(n)번 실행된다.

 

public String repeatString(String target, int n)

{

String result = "";

for(int i=0; i < n; i++ )

result += target;

return result;

} 

 

 

반면, 아래 코드처럼 StringBuffer를 사용하도록 바꾸면 new 연산자는 한번만 실행된다.


 

public static StringBuffer repeatString(String target, int n)

{

StringBuffer result = new String Buffer();

for(int i=0; i<n; i++)

result.append(target);

return result;

}

 

 

StringBuffer의 경우 String과 마찬가지로 내부적으로 char[]을 사용하지만 이 값은 변경이 가능하다.

StringBuilder StringBuffer와 매우 비슷하다. 소스 코드에서 Ctrl+F ReplaceAll을 해도 에러가 생기지 않는다.

(물론 happen to be 에러가 생길 수도 있다 ^^;; 두 클래스의 모든 메서드가 이름과 내용이 똑같다는 의미다)

 

두 클래스의 차이점은 StringBuilder는 동기화를 지원하지 않아 single thread에서만 사용 가능, StringBuffer는 동기화를 지원하기 때문에 multi-thread에서도 사용 가능하다는 점이다.

따라서 multi thread상황에서 문자열이 리소스로 사용된다면 선택의 여지 없이 StringBuffer를 사용, single thread 상황이라면 동기화를 고려하지 않는 StringBuilder를 이용해 더 빠른 performance를 취하는게 좋다고 알고 있다.

 

그 차이가 어느 정도 되는지 실험을 통해 비교해보도록 하자.

 

 

 

 

 

2. Source Code

 

 

실험 결과에 앞서, 먼저 다음과 같은 더러운 소스코드를 만들었다.

 

문자열 "a"를 반복해서 concaternation하는 간단한 프로그램이다.

 

실험변수1: String, StringBuffer, StringBuilder

실험변수2: concaternation 횟수 (String의 경우엔 +연산자, StringBuilder StringBuffer의 경우엔 append 메서드를 사용)

 

 

 

public class StringPerformance {

    public static double mkLongStr(int len)

    {

        long start = System.nanoTime();

       

        String str = "";       

        for(int i=0 ; i < len ; i++)    str += "a";

       

        long end = System.nanoTime();

        return (end-start)/(double)1_000_000_000;

    }

   

    public static double mkLongStrBuffer(int len)

    {

        long start = System.nanoTime();

       

        StringBuffer sb = new StringBuffer();

        for(int i=0 ; i < len ; i++)    sb.append("a");

       

        long end = System.nanoTime();

        return (end-start)/(double)1_000_000_000;

    }

   

    public static double mkLongStrBuilder(int len)

    {

        long start = System.nanoTime();

       

        StringBuilder sb = new StringBuilder();

        for(int i=0 ; i < len ; i++)    sb.append("a");

       

        long end = System.nanoTime();

        return (end-start)/(double)1_000_000_000;

    }

   

    public static void main(String[] args) {

        int[] lenList = new int[]{10_000, 20_000, 40_000, 80_000, 160_000, 320_000, 640_000,

                                    1_280_000, 2_560_000, 5_120_000, 10_240_000, 20_480_000, 40_960_000, 81_920_000, 163_840_000};

       

        for(int len : lenList)

        {

// System.out.println("String\t" + len + "\t" + mkLongStr(len));

            System.out.println("StringBuffer\t" + len + "\t" + mkLongStrBuffer(len));

            System.out.println("StringBuilder\t" + len + "\t" + mkLongStrBuilder(len));

        }

    }

}

 

 

 

 

 

3. Result

 

 


 

x: String의 경우 + 연산자의 사용 횟수, StringBuffer StringBuilder의 경우 append의 사용 횟수

y: 시간 (첫번째 그래프는 초(second), 두번째 그래프는 그 값(second) log2를 취한 값)

 

 

 

String 16만번을 넘어서니 너무 느려서 사용할 수가 없었다.

StringBuffer StringBuilder는 굉장히 좋은 성능을 보여준다.

String x축 값이 2배 늘어날 때 y축 값이 2.5~5배 정도 늘어난다. (consistent하지 않음)

StringBuffer, StringBuilder x축 값이 2배 늘어날 때 y축 값이 2배 정도 늘어난다.

StringBuilder StringBuffer 2배 정도 더 빠르다

 

 

4. Conclusion

 

 

 

String + 연산자는 횟수가 많아지면 사용이 불가능할 정도로 성능이 좋지 않다.

문자열 가지고 할 일이 많다면 반드시 StringBuffer StringBuilder를 사용하도록 하자.

StringBuffer StringBuilder보다 2배 정도 느리다고 해도 append 163,840,000 ( 17천번..) 하는데 2.65초밖에 걸리지 않는다. single thread의 웬만한 상황에선 둘 중 아무거나 써도 될 듯 한데 뭐사용하는 사람 마음이겠다