[Java] String, StringBuffer, StringBuilder 차이

Posted by ironmask84
2016. 6. 17. 17:18 나는 프로그래머다!/Java & Android


0. 들어가기 전에

 자바에서 String 문자를 더해가는건 큰 이슈가 있습니다. 보통 사람들은 String 에서 +를 이용하여 문자를 결합합니다. 이 방법은 작은 규모의 몇백자 정도로는 그렇게 큰 차이를 보이지 않을지도 모르지만 잘 모르고 쓴다면 작은 규모에서도 큰 차이를 보여줍니다. 이번에는 그 차이를 한번 따져보려고 합니다.  

 

1. 이론적인 차이

 

 1-1. String의 경우 

 

자바에서 String은 Immutable(불변의) 속성을 가지고 있습니다. 우리는 보통 String을 저장할 때 이렇게 합니다.

 

 String name = "펭귄";  

 name="치킨";  // 펭귄을 치킨으로 바꿨습니다.

 

 

위와 같이 한다면 name에 있던 펭귄이라는 치킨으로 수정되는 것 처럼 보이지만 내부적으로는 수정되는 것이 아니라 새로운 메모리를 잡고 치킨이라는 값을 저장한 후 그 곳을 가리키는 방식이 됩니다.

 

 

 

 

 + 연산자로 더하는 작업도 마찬가지입니다.

 

 String name = "펭귄";

 name = "심해"+ name; //결과 값은 심해펭귄이라고 저장됩니다.

 

새로운 메모리를 할당하여 그곳에 심해펭귄 이라는 글자를 저장하고 name이 그곳을 가리키게 한 후 기존의 펭귄이 저장된 메모리를 Garbage Collection이 제거해줍니다. 

 

 

 

단순히 문자를 몇개 더하는 경우도 이 과정을 거치지만 몇번 이내로 끝나는 횟수라면 굳이 다른 것을 쓰지 않으셔도 됩니다. (StringBuilder가 빠르지만 ns 단위의 차이입니다.) 다만 긴 문자를 몇 백번 이상 더할 경우에는 성능이 내려갈 수 있으므로 StringBuilder나 StringBuffer를 써주시는게 좋습니다. 

 

JDK5 이상의 버전부터는 String도 내부적으로 StringBuilder로 바꿔서 컴파일한다고는 하는데 막상 해보면 그다지 차이가 없습니다. 

 

 

 

 

 

1-2. StringBuilder와 StringBuffer의 경우

 

 StringBuilder와 StringBuffer는 String 과 다르게 가변의 속성을 가지고 있습니다. 그래서 할당된 공간에 계속 이어가는게 가능하며, append 메소드를 이용하여 문자를 추가합니다. 또한 할당된 공간이 오버플로우가 발생한 경우에는 자동으로 할당된 공간을 확장합니다. 

(이때 오버해드가 발생하여 속도가 약간 저하되는 듯 합니다.)

 

 StringBuilder의 경우 StringBuffer와 달리 동기화 된다는 보장을 하지 않지만 StringBuffer보다는 조금 빠른 속도를 보여줍니다.

만약 멀티 스레드를 사용하는 경우에는 동기화를 해야하는 경우가 생기는데  이 경우에는 StringBuffer를 이용하여야 합니다. StringBuffer는 멀티 스레드 작업에 대해서 안전하게 만들어져 있습니다. 하지만 일반적인 경우에는 멀티 스레드를 사용할 일이 별로 없으므로 StringBuilder를 사용하시면 되겠습니다.

 

 

 

 

2. 실제 속도 차이

 

속도를 계산하는 간단한 소스를 짜봤습니다. 글자수는 10자를 기준으로 하여 횟수를 조정하여 총 100자부터 10만자까지 더하도록 하겠습니다. 같은 시행횟수라도 시행때마다 값의 차이가 생깁니다. 하지만 그 차이는 그렇게 크지 않으며, 그 흐름 또한 일치하므로 오차가 조금 있을 수 있다 정도는 염두해 두시면 되겠습니다. 추출한 값은 ns로 추출하였지만 후반의 숫자가 매우 크므로 ms로 변환하여 표기하도록 하겠습니다.

 

 public class Main {

public static void main(String[] args) {

final int CYCLE_RATE = 10; //횟수 (10에서 10000까지 올릴겁니다) 

final String TEST_STR = "abcde12345";  //10자

for(int idx = 1; idx <= 10; idx ++){

System.out.print(idx*10*CYCLE_RATE +"\t\t"); //총 횟수

}

System.out.println("\n");

for(int idx = 1; idx <= 10; idx ++){

StringBuilderTest(CYCLE_RATE * idx, TEST_STR);

}

System.out.println("\n================================================================");

for(int idx = 1; idx <= 10; idx ++){

StringBufferTest(CYCLE_RATE * idx, TEST_STR);

}

System.out.println("\n================================================================");

for(int idx = 1; idx <= 10; idx ++){

StringTest(CYCLE_RATE * idx, TEST_STR);

}

}

public static void StringTest(int cycle, String txt){

long before = System.nanoTime();

String testString = "";

for(int idx = 0; idx < cycle; idx++){

testString += txt; //20자

}

long after = System.nanoTime();

System.out.print((after - before)+"\t\t");

}

public static void StringBuilderTest(int cycle, String txt){

long before = System.nanoTime();

String testStringBuilder = "";

StringBuilder builder = new StringBuilder();

for(int idx = 0; idx < cycle; idx++){

builder.append(txt);

}

testStringBuilder = builder.toString();

long after = System.nanoTime();

System.out.print((after - before)+"\t\t");

}

public static void StringBufferTest(int cycle, String txt){

long before = System.nanoTime();

String testStringBuffer = "";

StringBuffer buffer = new StringBuffer();

for(int idx = 0; idx < cycle; idx++){

buffer.append(txt);

}

testStringBuffer = buffer.toString();

long after = System.nanoTime();

System.out.print((after - before)+"\t\t");

}

}

 

 

10개자리 문자를 시행횟수만큼 더한 결과입니다.

문자를 10개 정도 더해도 String이 builder나 buffer보다 3-4배 정도 느린걸 알 수 있습니다. StringBuffer의 경우 처음 버퍼를 생성하는 시간 때문인지 첫 시행때 다소 시간이 걸립니다.

builder와 buffer가 속도가 느려졌다 빨라졌다 하는건 할당한 메모리가 다 차서 메모리를 늘려주는 작업을 하기 때문인 듯 합니다. 

 

 시행횟수

 10

 20

 30

 40

 50

 60

 70

 80

 90

 100

 StringBuilder

 0.009 ms

 0.012 ms

 0.016 ms

 0.027 ms

 0.03 ms

 0.03 ms

 0.039 ms

 0.037 ms

0.076  ms

 0.04 ms

 StringBuffer

 0.048 ms

 0.011 ms

 0.018 ms

 0.021 ms 

 0.023 ms 

0.027 ms  

 0.039 ms

 0.029 ms

 0.032 ms 

 0.04 ms

 String

 0.022 ms

 0.039 ms

 0.059 ms 

0.116 ms

 0.063 ms

0.077 ms 

0.133 ms 

 0.213 ms

0.129 ms

0.149 ms 

 

 

시행횟수가 백번 이상으로 넘어가면 속도차이가 확연하게 벌어집니다. Builder와 Buffer는 그렇게 큰 차이가 나진 않지만 String과의 차이는 40배 이상으로 벌어졌습니다.

 시행횟수

 100

 200

 300

 400

 500

 600

 700

 800

 900

 1000

 StringBuilder

 0.047 ms

 0.09 ms

 0.14 ms

 0.14 ms

 0.19 ms

 0.15 ms

 0.1 ms

 0.056 ms

0.056  ms

 0.076 ms

 StringBuffer

 0.050 ms

 0.021 ms

 0.032 ms

 0.040 ms 

 0.056 ms 

0.066 ms  

 0.052 ms

 0.057 ms

 0.061 ms 

 0.078 ms

 String

 0.15 ms

 0.44 ms

 2.3 ms 

0.85 ms

 2.9 ms

1.9 ms 

1.7 ms 

 2.2 ms

2.8 ms

3.5 ms 

 

 

시행횟수가 1000이 넘어가면 String과의 격차는 몇백배 입니다.

 시행횟수

 1000

 2000

 3000

 4000

 5000

 6000

 7000

 8000

 9000

 10000

 StringBuilder

 0.44 ms

 0.52 ms

 0.19 ms

 0.27 ms

 0.22 ms

0.24 ms

 0.37 ms

 0.45 ms

 0.38 ms

 1.74 ms

 StringBuffer

 0.14 ms

 0.17 ms

 0.18 ms

 0.23 ms 

 0.24 ms 

 0.26 ms  

 0.33 ms

 0.33 ms

 1.65 ms 

 0.4 ms

 String

 5.8 ms

 16.9 ms

35.1 ms 

66.4 ms 

 100.98 ms

138.43 ms 

190.3 ms 

 266.6 ms

331.78 ms 

417.3 ms

 

 

시행횟수가 10만이 되면 String은 너무 느려서 쓸 수 없습니다. 1000ms = 1초인데 62000ms라면 62초나 걸리게 됩니다.

문자가 200만개가 되니 610000 ms가 걸렸습니다. 610초 즉 10분입니다.

 시행횟수

 100000

 200000

 300000

 400000

 500000

 600000

 700000

 800000

 900000

 1000000

 StringBuilder

 9.7 ms

 8.8 ms

 18.6 ms

 15.2 ms

 29 ms

23.7 ms

 29.5 ms

 37.4 ms

 34.7 ms

 56.2 ms

 StringBuffer

 5.6 ms

 13 ms

 15.9 ms

 23.7 ms 

 25.9 ms 

 32.6 ms  

 40.6 ms

 39.5 ms

 48.7 ms 

 60.7 ms

 String

 62024 ms

  613483 ms

 ...ms 

 ...ms 

 ... ms

 ...ms 

 ...ms 

 ... ms

... ms 

... ms

 

 

덤으로 1개짜리 문자를 10000번 더하는 것이 100개짜리 문자를 100번 더하는것보다는 오래 걸리며, 100자짜리 문자를 100번 더하는 것이, 10자짜리 문자를 100번 더하는 것보다 오래 걸립니다.


펌 : http://blog.naver.com/platinasnow/220197411183


** CharSequences 인터페이스

CharSequences는 인터페이스로 String/StringBuffer/StringBuilder 세가지 클래스는 모두 

CharSequences 인터페이스를 상속받고 있습니다.


때문에 Charsequence에는 위 3가지 클래스를 모두 수용할 수 있습니다.(Up-casting)

또한 사용할 때에는 down-casting 해서 사용하시면 됩니다.


String/StringBuffer/StringBuilder 세가지 클래스를 매개변수로사용하는 함수라면 매개변수를 Charsequence로 해주는 좋겠죠?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CharSequenceTest {
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        anythingString("a");
        anythingString(new StringBuffer("a"));
        anythingString(new StringBuilder("a"));
    }
    
    public static void anythingString(CharSequence c){
        System.out.println(c);
    }
   
}
 
 

펌 : http://blog.naver.com/tkddlf4209/220706609022