HOWTO · Java

Java I/O 출력에서 flush() 메서드 사용하기

flush()가 버퍼링된 Java I/O 출력을 다음 계층으로 전달하는 방법을 알아봅니다.

이 페이지의 내용

Java 개발자는 출력 객체를 닫기 전에 버퍼에 쌓인 출력을 다음 계층으로 전달해야 할 때 flush()를 사용합니다. 이 메서드는 Java I/O의 Writer와 OutputStream에 속하며, 시퀀스를 처리하는 Java Stream API의 기능은 아닙니다. flush()를 호출해도 파일이 디스크에 영구 저장되거나 원격 서버가 바이트를 수신하거나 처리한다는 보장은 없습니다.

Java I/O에서 flush()가 하는 일

출력 클래스는 하위 대상에 대한 호출 횟수를 줄이기 위해 쓰기 데이터를 메모리에 모아 둘 수 있습니다. flush()를 호출하면 해당 출력 객체가 대기 중인 데이터를 다음 계층으로 전달하도록 요청합니다. 예를 들어 BufferedOutputStream은 래퍼에 쌓인 바이트를 감싼 스트림으로 전달합니다. 하위 계층도 별도로 데이터를 버퍼링할 수 있으므로, 한 객체를 flush해도 연결된 모든 버퍼가 비워지는 것은 아닙니다. flush는 대기 중인 데이터를 다음 계층으로 보내고 버퍼링 계층에서 사용 중이던 공간을 다시 사용할 수 있게 합니다. 그러나 버퍼 자체를 해제하지 않으며, 하위 계층으로의 전달이나 영구 저장을 보장하지도 않습니다. 또한 flush만으로 데이터 무결성이 보장되거나 출력 손상이 방지되는 것은 아닙니다. 무결성이 중요하다면 애플리케이션에 필요한 체크섬 검증이나 메시지 형식 검사 같은 확인 절차를 사용하세요.

Writer는 문자 출력을 대상으로 이에 해당하는 작업을 수행합니다. Writer는 데이터를 하위 스트림으로 전달하면서 문자를 바이트로 인코딩할 수 있습니다. 정확한 버퍼링 동작은 연결된 객체에 따라 달라집니다. 나중에 쓰기 작업을 하거나 객체를 닫을 때까지 기다리지 않고 출력을 지금 다음 계층으로 전달해야 할 때 flush()가 유용합니다.

flush()를 호출할 때

의미 있는 전달 시점에 flush하세요. 예를 들어 사용자 입력을 기다리기 전에 프롬프트를 쓴 뒤, 다른 프로세스가 곧 읽어야 할 로그 레코드를 출력한 뒤, 또는 버퍼링된 소켓 Writer를 통해 메시지 전체를 보낸 뒤가 이에 해당합니다. 파일이나 처리량이 중요한 다른 대상에 작은 데이터를 여러 번 쓰는 프로그램에서 매번 flush하면 버퍼링의 이점이 줄고 오버헤드가 늘 수 있습니다. 중간에 출력 내용이 보여야 할 필요가 없다면 일반적인 버퍼링과 리소스 종료에 맡기세요.

Writer에서 flush() 사용하기

이 독립 실행형 Java 예제는 BufferedWriter와 PrintWriter를 거쳐 StringWriter에 씁니다. 출력되는 값은 명시적으로 flush한 뒤 대상에서 읽을 수 있는 텍스트입니다. 예제의 코드와 출력에 표시되는 레이블은 바이트 일치 규칙에 따라 영어 그대로 유지됩니다.

예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.StringWriter;

public class FlushWriterExample {
    public static void main(String[] args) throws java.io.IOException {
        StringWriter destination = new StringWriter();
        try (PrintWriter writer = new PrintWriter(new BufferedWriter(destination))) {
            writer.println("status=ready");
            writer.flush();
            System.out.print(destination.toString());
        }
    }
}
status=ready

BufferedWriter를 사용하면 버퍼링 계층이 분명해집니다. 바깥쪽 PrintWriter를 flush하면 문자가 이 Writer를 거쳐 StringWriter로 전달되고, 대상을 출력하면 결과 줄을 볼 수 있습니다. StringWriter는 간단한 시연을 위한 메모리 내 대상입니다. 파일이나 소켓에 쓰는 Writer는 하위 계층에서 다르게 동작할 수 있습니다.

BufferedOutputStream에서 flush() 사용하기

바이트 출력에서는 대기 중인 바이트를 감싼 스트림으로 전달해야 할 때 버퍼링 래퍼를 flush하세요. 이 독립 실행형 예제는 메모리 내 바이트 대상을 사용하므로 파일이나 서비스가 필요하지 않습니다. 예제의 코드와 출력에 표시되는 레이블은 바이트 일치 규칙에 따라 영어 그대로 유지됩니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.nio.charset.StandardCharsets;

public class BufferedFlushExample {
    public static void main(String[] args) throws java.io.IOException {
        ByteArrayOutputStream destination = new ByteArrayOutputStream();
        try (BufferedOutputStream output = new BufferedOutputStream(destination)) {
            output.write("batch-42".getBytes(StandardCharsets.UTF_8));
            output.flush();
            System.out.println(new String(destination.toByteArray(), StandardCharsets.UTF_8));
        }
    }
}
batch-42

여기서는 flush()를 호출할 때 래퍼가 인코딩된 바이트를 메모리 내 스트림으로 전달합니다. 파일 스트림에서는 이 작업이 바이트를 운영 체제의 파일 계층으로 전달하지만, 저장 장치가 데이터를 기록했다는 보장은 하지 않습니다. 파일 데이터를 영구적으로 보존해야 한다면 애플리케이션에 적합한 파일 API와 내구성 옵션을 사용하세요.

네트워크 출력에서 flush() 사용하기

버퍼링된 소켓 Writer를 통해 요청이나 메시지를 쓸 때 상대편이 메시지를 기다리고 있다면 전체 메시지를 쓴 뒤 flush()를 호출하세요. 예를 들어 out.println("PING"); out.flush();는 Writer의 줄을 소켓 출력 경로로 전달합니다. 그렇다고 바이트가 상대편에 도착했거나, 읽혔거나, 처리되었다는 뜻은 아닙니다. 애플리케이션에서 확인이 필요하면 프로토콜 응답이나 확인 메시지를 사용하고, 주변 코드에서 소켓 오류와 시간 초과를 처리하세요. 네트워크 예제는 연결된 상대편이 필요하므로 짧은 코드는 단독 실행 가능한 프로그램이 아니라 동작을 설명하는 예시입니다.

flush()와 스트림 닫기의 관계

출력 스트림이나 Writer를 닫으면 일반적으로 닫기 과정의 일부로 flush도 수행됩니다. 나중의 작업이 일찍 끝나더라도 리소스가 닫히도록 try-with-resources를 사용하세요. 닫기가 바로 다음 작업이고 중간에 데이터를 읽어야 하는 곳이 없다면 닫기 전에 명시적으로 flush할 필요는 없습니다. 리소스를 열어 둔 채 출력을 공개해야 할 때는 명시적 flush가 유용합니다. flush와 close 모두 모든 애플리케이션 또는 시스템 장애로부터 보호해 주지는 않으므로, 필요한 내구성이나 확인 응답을 대신하는 것으로 취급하지 마세요.