[Java] String, StringBuffer, StringBuilder, BufferedWriter 비교

💡 String 이란?

  • java.lang에 있다.
  • 한번 정해지면 변경불가능한 불변(immutable) 클래스다.
  • String 객체가 생성되면 그 값은 변경되는게 아니라 새로운 객체로 추가 및 변경이 된다.

💡 StringBuffer 란?

  • java.lang에 있다.
  • StringBuffer는 변경이 가능한 가변(mutable) 클래스다.
  • 멀티 쓰레드 환경에서 동시에 같은 문자열 인스턴스에 접근할 때 중복 점유를 막을 수 있는 장치가 되어 있다.
  • 위의 장치 때문에 동기화 과정에서 성능 저하가 발생할 수 있다.

💡 StringBuilder 란?

  • java.lang에 있다.
  • StringBuilder는 변경이 가능한 가변(mutable) 클래스이다.
  • 멀티쓰레드 환경에서 불안정적이지만 일반적 환경에서는 StringBuffer처럼 동기화로 인한 성능저하가 일어나지 않기 때문에 가장 빠르다.

💡 BufferedWriter 란?

  • java.io에 있다.
  • 선언이 필요하다.
  • throws Exception 예외처리가 필요하다.
  • 버퍼를 잡아 놓았기 때문에 반드시 사용한 후에, flush()/ close()를 해주어야 한다.

💡 문자열 더하기

String a = "나는 ";
String b = "말하는 ";
String c = "감자";

// String +
String str = a + b + c;
System.out.println(str);	 // 출력 : 나는 말하는 감자

// StringBuffer.append()
StringBuffer sb = new StringBuffer();
sb.append(a).append(b).append(c);
System.out.println(sb); 	// 출력 : 나는 말하는 감자

// StringBuilder.append()
StringBuilder sb2 = new StringBuilder();
sb2.append(a).append(b).append(c);
System.out.println(sb2); 	// 출력 : 나는 말하는 감자

//BufferedWriter
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write(a);
bw.write(b);
bw.write(c);
bw.flush();   // 출력 : 나는 말하는 감자
bw.close();
//toString()이나 valueOf()을 이용해 둘 다 String값에 대입이 가능하다.
String str1 = sb1.toString();
String str2 = sb2.toString();

💡 반복문 적용

백준 2751번 수 정렬하기 2

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;

public class Main {

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		ArrayList<Integer> list = new ArrayList<Integer>();
		
		int N =	Integer.parseInt(br.readLine());
		for(int i=0; i<N; i++) {
			list.add(Integer.parseInt(br.readLine()));
		}
		
		//오름차순 정렬
		Collections.sort(list);
		
		//StringBuilder 사용
		StringBuilder sb = new StringBuilder();
		
		//향상된 for문 사용 (list)
		for(int value : list) {
			sb.append(value).append('\n');
		}
		
		//StringBuilder 출력
		System.out.println(sb);	
		
	}

}
  • 아래 3개의 시간초과 모두 StringBuilder를 사용하지 않고 String을 사용해 더해서 생긴 결과다.
  • 위에서 두번째 결과는 위 코드에서 BufferedReader를 쓰지 않고 Scanner을 썼던 결과다.
  • 맨 위의 결과가 위의 코드의 결과이다. BufferedReader + StringBuilder을 사용해 빠른 결과를 도출했다.
  • Scanner와 BufferedReader 의 비교는 추후에 글로 작성할 예정이다.

💡 특징 비교

  String StringBuffer StringBuilder BuffereWriter
클래스 불변 가변 가변 가변
문자열 변경 str += “추가” sb.append(“추가”); sb.append(“추가”); bw.write(“추가”);
속도 매우 매우 매우 느림 빠름 가장 빠름 보통
추천 사용 방법 문자열의 변경이나 추가가 없는경우 멀티쓰레드 환경 단일쓰레드 환경  

💡 상세 속도 비교

출처 : javapapers.com

  • y축은 걸리는 시간으로 기울기가 가장 낮은 StringBuilder가 가장 빠르다.
  • 다음은 0을 반복적으로 더해서 직접 걸리는 시간을 구해보는 코드다.
package Test;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class Time {

	public static void main(String[] args) throws IOException {

		long now;
		
		// String 
		now = System.currentTimeMillis();
		String test = "";
		for (int i = 0; i < 300000; i++) {
			test += "0";
		}
		System.out.println(test);
		System.out.println("String 연산시간 : " + (System.currentTimeMillis()-now));
		
		// StringBuffer
		now = System.currentTimeMillis();
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < 300000; i++) {
			buffer.append("0");
		}
		System.out.println(buffer);
		System.out.println("StringBuilder 연산시간 : " + (System.currentTimeMillis()-now));
		
		// StringBuilder
		now = System.currentTimeMillis();
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < 300000; i++) {
			builder.append("0");
		}
		System.out.println(builder);
		System.out.println("StringBuffer 연산시간 : " + (System.currentTimeMillis()-now)); 
		
		// BufferedWriter
		now = System.currentTimeMillis();
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		for (int i = 0; i < 300000; i++) {
			bw.write("0");
		}
		bw.newLine();
		bw.flush();
		System.out.println("BufferedWriter 연산시간 : " + (System.currentTimeMillis()-now));
		
	}

}
//출력 결과
000000000...(300000개)
String 연산시간 : 6267~6500 (너무 느려서 3번만 돌려봄)

000000000...(300000개)
StringBuilder 연산시간 : 25~49 (10회 돌려본 결과)

000000000...(300000개)
StringBuilder 연산시간 : 16~28 (10회 돌려본 결과)

000000000...(300000개)
BufferedWriter 연산시간 : 24~38 (10회 돌려본 결과)

💡 결론

  • String은 문자열을 변경하거나 추가할 때 사용하면 반복이 많아지고 데이터가 커질수록 속도 차이가 많이 나서 쓰지 않는게 좋다.
  • 귀찮더라도 문자열을 더하는 경우가 많으면 StringBuilder을 사용하는 습관을 들이자.
  • 백준에서 문제를 풀었는데 시간초과가 발생한다면 String을 사용했는지부터 보고 고치자.