System.out.print vs BufferedWriter - 왜 BufferedWriter가 더 빠를까

이재훈·2025년 2월 2일

자바로 코딩 테스트를 시작한 지 얼마 되지 않았을 때 문제가 생겼다. 코드의 결과는 맞췄는데, 아무리 고민해도 시간 초과를 해결할 수 없었다. 찾아보니 System.out.print 계열 메서드가 오버헤드가 있어서 BufferedWriter + StringBuilder 조합을 사용해야 했다. 당시에는 문제 풀기에 급급해서 그냥 사용했는데, 이제는 왜 그런지 정확히 이해해보자.


1. System.out.println의 동작 구조

System.out.println("hello world");

위 코드는 단순한 출력 코드이지만, 내부 동작을 깊이 파고들어 보자.

// PrintStream.java
public void println(String x) {  
    if (getClass() == PrintStream.class) {  
        writeln(String.valueOf(x));  
    } else {  
        synchronized (this) {  
            print(x);  
            newLine();  
        }  
    }  
}

System.out.println()PrintStreamprintln() 메서드를 통해 출력된다. 상속 여부를 체크한 후, 내부적으로 writeln()을 호출한다.

// PrintStream.java
private void writeln(String s) {  
    try {  
        synchronized (this) {  
            ensureOpen();  
            textOut.write(s);  
            textOut.newLine();  
            textOut.flushBuffer();  // 즉시 플러시 발생
            charOut.flushBuffer();  
            if (autoFlush)  
                out.flush();  
        }  
    } catch (IOException x) {  
        trouble = true;  
    }  
}

System.out.print는 즉시 출력이 이루어지도록 flushBuffer()를 호출하여 I/O 호출이 자주 발생하게 된다.


2. BufferedWriter의 동작 방식

// BufferedWriter.java
public void write(char cbuf[], int off, int len) throws IOException {  
    synchronized (lock) {  
        ensureOpen();  
        if (len >= nChars) {  
            flushBuffer();  
            out.write(cbuf, off, len);  
            return;  
        }  
        int b = off, t = off + len;  
        while (b < t) {  
            int d = Math.min(nChars - nextChar, t - b);  
            System.arraycopy(cbuf, b, cb, nextChar, d);  
            b += d;  
            nextChar += d;  
            if (nextChar >= nChars)  
                flushBuffer();  
        }  
    }  
}

BufferedWriter버퍼(기본 8192바이트)를 사용하여 일정 크기 이상이 되면 한 번에 출력하기 때문에 I/O 호출 횟수가 줄어들어 성능이 향상된다.


3. 성능 비교 실험

import java.io.*;

public class PrintVsBufferedWriter {
    public static void main(String[] args) throws IOException {
        int N = 8192 * 10; // 출력 횟수  

        // System.out.print 사용  
        long start1 = System.currentTimeMillis();  
        for (int i = 0; i < N; i++) {  
            System.out.print(i + " ");  // 즉시 출력됨  
        }  
        long end1 = System.currentTimeMillis();  
        long printTime = end1 - start1;  // 실행 시간 저장  

        // BufferedWriter 사용  
        long start2 = System.currentTimeMillis();  
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));  
        for (int i = 0; i < N; i++) {  
            bw.write(i + " ");  // 버퍼에 저장됨  
        }  
        bw.flush();  // 최종 출력  
        long end2 = System.currentTimeMillis();  
        long bufferedTime = end2 - start2;  // 실행 시간 저장  

        // 최종 결과 비교 출력  
        System.out.println("\n===== 성능 비교 결과 =====");  
        System.out.println("System.out.print 실행 시간: " + printTime + "ms");  
        System.out.println("BufferedWriter 실행 시간: " + bufferedTime + "ms");  
        System.out.println("=========================");  

        bw.close();  
    }
}

실행 결과 (환경에 따라 다를 수 있음)

===== 성능 비교 결과 =====
System.out.print 실행 시간: 325ms
BufferedWriter 실행 시간: 22ms
=========================

4. 결론

비교 항목System.out.print()BufferedWriter.write()
출력 방식즉시 OS에 출력버퍼에 저장 후 한 번에 출력
버퍼 사용 여부없음있음 (기본 8KB)
I/O 호출 횟수많음 (출력할 때마다 OS 접근)적음 (버퍼가 찼을 때만 OS 접근)
성능느림빠름

결론

  • System.out.print즉시 플러시(flush)되므로, 한 글자라도 출력할 때마다 OS에 요청을 보냄 → 성능 저하.
  • BufferedWriter버퍼를 사용하여 일정 크기 이상이 될 때만 출력I/O 요청 횟수가 줄어들어 성능이 크게 향상됨.
  • 코딩 테스트에서 대량의 출력을 처리할 때는 반드시 BufferedWriter를 사용해야 함.

0개의 댓글