코딩 테스트 일지 📒

[코테/JAVA] 효율적인 출력 방법: 언제 BufferedWriter, 언제 System.out.print, 언제 StringBuilder?

코양이🤍 2024. 9. 23. 17:57

코딩 테스트에서 출력 속도는 생각보다 중요한 요소입니다.

특히 반복문을 통한 대량의 데이터를 출력해야 할 때, 출력 방식에 따라 프로그램의 성능 차이가 발생할 수 있습니다.

이 글에서는 자바에서 자주 사용하는 두 가지 출력 방법인 BufferedWriter와 System.out.print, 그리고 StringBuilder의 차이를 설명하고, 상황에 따라 어떤 방식이 더 적합한지 알려드리려고 합니다!


⭐ BufferedWriter

BufferedWriter는 데이터를 버퍼에 담아 한 번에 출력하는 방식으로, 출력할 데이터가 많을 경우 특히 유용합니다.

한 번에 많은 데이터를 처리하여 I/O 작업의 빈도를 줄이므로 성능이 더 뛰어납니다.

import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        bw.write("BufferedWriter를 사용한 출력 예시");
        bw.newLine();  // 줄 바꿈
        bw.write("빠른 출력이 필요할 때 사용합니다.");
        bw.flush();  // 남은 데이터를 모두 출력
        bw.close();  // 스트림 종료
    }
}

장점

  • 빠른 출력: 많은 데이터를 처리할 때는 출력 속도가 System.out.print보다 훨씬 빠릅니다.
  • 버퍼 사용: 작은 출력 작업이 여러 번 반복되는 상황에서 성능 저하를 방지할 수 있습니다.

단점

  • 복잡성: BufferedWriter는 사용 시 flush()나 close()를 명시적으로 호출해야 하므로, 코딩이 약간 더 복잡해질 수 있습니다.

 

⭐ System.out.print

System.out.print는 자바에서 가장 기본적인 출력 방식입니다. 하지만 출력할 때마다 즉시 콘솔에 데이터를 보내기 때문에, 작은 데이터를 자주 출력할 경우 성능이 떨어질 수 있습니다.

public class SystemOutPrintExample {
    public static void main(String[] args) {
        System.out.print("System.out.print를 사용한 출력 예시");
        System.out.print("\n");  // 줄 바꿈
        System.out.print("간단한 출력이 필요할 때 사용합니다.");
    }
}

장점

  • 간단함: 별도의 추가 작업 없이 바로 사용할 수 있어 간편합니다.
  • 즉시 출력: 바로바로 콘솔에 출력되므로, 디버깅할 때 실시간 결과를 확인하기 좋습니다.

단점

  • 느린 성능: 많은 데이터를 출력할 때는 BufferedWriter에 비해 성능이 떨어집니다. 특히 반복문에서 자주 호출하면 병목 현상이 발생할 수 있습니다.

 

⭐ StringBuilder

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
System.out.println(sb);  // 출력: Hello World

 

  • 장점:
    • 문자열을 자주 수정하거나 연결할 때 메모리 효율적.
    • 성능이 좋음(String 대신 사용하면 성능 개선).
  • 단점:
    • 동기화가 없으므로 멀티스레드 환경에서는 안전하지 않음. (이럴 땐 StringBuffer 사용)

 

 


 

 ✅ StringBuilder vs BufferedWriter vs System.out.print 차이점

  • StringBuilder는 메모리 상에서 문자열을 수정하거나 결합할 때 사용하며, 파일이나 콘솔에 직접 출력하지 않습니다.
  • BufferedWriter는 파일 출력에 최적화된 방법으로, 많은 양의 데이터를 효율적으로 출력할 때 사용합니다.
  • System.out.print는 즉시 콘솔에 출력하는 가장 단순한 방법으로, 소량의 데이터를 출력할 때 사용합니다.

 

🤔 언제 무엇을 사용해야 할까?

BufferedWriter 를 사용해야 하는 경우

대량의 데이터를 출력해야 하는 경우

예를 들어 코딩 테스트에서 여러 줄을 출력하거나, 반복문을 통해 수백만 개 이상의 데이터를 출력할 때 적합합니다. 버퍼링을 통해 효율적으로 데이터를 처리할 수 있기 때문입니다.

System.out.print 를 사용해야 하는 경우

단일 출력이거나, 출력할 데이터의 양이 적고, 코드의 간결함을 중시할 때.

또한 디버깅할 때 실시간으로 값을 확인하고 싶을 때도 유용합니다.

StringBuilder를 사용해야 하는 경우

문자열을 자주 수정하거나 결합해야 하는 경우

반복문을 통해 여러 문자열을 추가하고, 최종적으로 하나의 문자열로 결합해야 할 때 적합합니다. String은 불변이기 때문에 새로운 문자열이 생성될 때마다 메모리를 할당하지만, StringBuilder는 메모리를 효율적으로 관리하며 문자열을 빠르게 수정할 수 있습니다.

사용 시기 요약

  • StringBuilder: 문자열을 자주 수정하거나 조작할 때.
  • BufferedWriter: 많은 양의 데이터를 파일에 출력할 때.
  • System.out.print: 콘솔에 간단히 출력할 때.

 

⭐ 백준에서 실제 시간 차이

위에부터 StringBuilder 사용, BufferedReader 사용, System.out.print 사용한 코드

이 문제는 https://www.acmicpc.net/problem/3460 로 입력값에 따라 출력을 많이 할수도 있는 문제였는데요

역시 출력을 많이 할 수 있어서 그런지, System.out.println보다 StringBuilder와 BufferedReader가 시간복잡도가 더 낮은 것을 확인할 수 있습니다. 

하지만 BufferedReader가 외부 데이터를 버퍼링하며 읽어오기 때문에 버퍼 메모리 공간이 추가로 필요합니다. 이로 인해 StringBuilder보다 공간 복잡도가 더 높으나 걸 확인할 수 있습니다.

아래는 코드입니다.

1. StringBuilder를 사용한 코드

import java.io.*;
import java.util.*;

public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringBuilder sb = new StringBuilder();
        
        int n = Integer.parseInt(br.readLine()); 
        int[] a = new int[n];
        
        for (int i = 0; i<n; i++){ 
            ArrayList<Integer> aa = new ArrayList<>();
            a[i]=Integer.parseInt(br.readLine()); 
            
            while(a[i]>=2){
                aa.add(a[i]%2);
                a[i]/=2;
            }
            
            aa.add(a[i]);
            
            for (int j = 0; j<aa.size(); j++){
                if(aa.get(j)==1) sb.append(j+" ");
            }
            sb.append("\n");
        }
        
        System.out.println(sb);
        
    }
}

 

2. BufferedReader를 사용한 코드

import java.io.*;
import java.util.*;

public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        int n = Integer.parseInt(br.readLine()); 
        int[] a = new int[n];
        
        for (int i = 0; i<n; i++){ 
            ArrayList<Integer> aa = new ArrayList<>();
            a[i]=Integer.parseInt(br.readLine()); 
            
            while(a[i]>=2){
                aa.add(a[i]%2);
                a[i]/=2;
            }
            
            aa.add(a[i]);
            
            for (int j = 0; j<aa.size(); j++){
                if(aa.get(j)==1) bw.write(j+" ");
            }
            bw.write("\n");
        }
        
        bw.flush();
        br.close();
        
    }
}

 

3. System.out.print를 사용한 코드

import java.io.*;
import java.util.*;

public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        int n = Integer.parseInt(br.readLine()); 
        int[] a = new int[n];
        
        for (int i = 0; i<n; i++){ 
            ArrayList<Integer> aa = new ArrayList<>();
            a[i]=Integer.parseInt(br.readLine()); 
            
            while(a[i]>=2){
                aa.add(a[i]%2);
                a[i]/=2;
            }
            
            aa.add(a[i]);
            
            for (int j = 0; j<aa.size(); j++){
                if(aa.get(j)==1) System.out.print(j+" ");
            }
            System.out.println();
        }
        
    }
}

이처럼 코테 문제에서 출력 방식을 어떻게 선택하느냐에 따라 시간 복잡도와 공간 복잡도를 줄일 수 있으니 고민해보고 가장 적합한 방식을 선택하는 것이 중요합니다!

앞으로도 화이팅~!