all-i-want.tistory.com

[JAVA] 입출력 (java.io) 본문

Debut or Die/자바

[JAVA] 입출력 (java.io)

hye2021 2025. 3. 31. 02:16

스트림 (stream)

  • 데이터를 전달하는 통로
  • 단방향 통신만 가능
  • 입력과 출력을 동시에 처리할 수 없음
    • 입력과 출력을 동시에 처리하려면 input stream과 output stream 2개의 스트림이 필요
  • FIFO(First In First Out) 구조

보조 스트림

  • 스트림의 기능을 보완하기 위한 기능
  • 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없음
  • 보조스트림 만으로는 입출력을 처리할 수 없고, 스트림을 먼저 생성한 다음에 이를 이용해서 보조스트림을 생성

BufferedInputStream

  • 실제 기능은 FileInputStream이 수행
  • 보조스트임인 BufferedInputStream은 버퍼만을 제공함
  • 버퍼를 사용한 입출력과 그렇지 않은 경우는 성능 차이가 상당하기 때문에 대부분의 경우에 버퍼를 이용한 보조스트림 사용
// 기반 스트림 생성
FileInputStream fis = new FileInputStream("test.txt");

// 기반 스트림을 이용해서 보조스트림을 생성
BufferedInputStream bis = new BufferedInputStream(fis);

bis.read(); // 보조스트림인 BufferedInputStream으로부터 데이터를 읽음

버퍼 (Buffer)

  • 임시로 데이터를 담아둘 수 있는 일종의 큐
  • 바이트 단위의 데이터가 입력될 때마다 Stream은 즉시 전송하게 되는데 이것은 디스크 접근이나 네트워크 접근 같은 오버헤드가 발생
  • Buffer는 중간에서 입력을 모아서 한번에 출력함으로써 I/O 의 성능을 향상

문자 기반 스트림

  • 바이트 기반 스트림은 입출력의 단위가 1byte
  • java에서는 char형이 2byte임 → 문자 기반 스트림은 입출력의 단위가 2byte

java.io 패키지의 상속 구조/ 붉은색: 기본스트림/ 검은색: 보조스트림

표준입출력

  • 콘솔을 통한 데이터 입력과 데이터 출력을 의미
    • System.in
    • System.out
    • System.err
  • java.lang.System 클래스의 static 필드
  • JVM이 프로그램 시작 시점에 자동으로 초기화

실제 내부

public final class System {
    public static final InputStream in;
    public static final PrintStream out;
    public static final PrintStream err;

    static {
        // 이 부분은 JVM이 초기화하면서 실행됨
        registerNatives(); // 네이티브 메서드 등록
        in = new BufferedInputStream(new FileInputStream(FileDescriptor.in));
        out = new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.out)), true);
        err = new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.err)), true);
    }
}
  • FileDescriptor.in, FileDescriptor.out, FileDescriptor.err
    • OS 레벨의 표준 스트림 (0, 1, 2) 의미

표준 입출력 매서드가 느리다고 여겨지는 이유

  1. 추상화 레벨이 높음
    • System.out.println() ➡️ PrintStream ➡️ OutputStream ➡️ FileOutputStream ➡️ FileDescriptor.out
    • 중간에 객체 생성, 메서드 호출, 동기화(synchronized) 등이 포함
  2. PrintStream 의 대부분의 매소드는 synchronized 이기 때문에 오버헤드 발생
    • 싱글스레드에서도 락 ➡️ 불필요한 비용
  3. 잦은 flush
    • 기본적으로 버퍼는 존재해요 (BufferedOutputStream, BufferedInputStream 사용)
    • PrintStream 생성자에서 autoFlush = true로 설정돼 있음
    • 줄 바꿈이 일어나거나 println을 쓸 때마다 버퍼를 비움
  4. 비교 대상이 훨씬 빠르기 때문
    • BufferedWriter + FileWriter
    • BufferedReader + FileReader
    • 버퍼 크기를 크게 설정할 수 있고,
    • flush 제어가 가능하고,
    • 동기화 제어도 직접 할 수 있어서 더 빠르다

개선안

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  1. System.in은 byte stream
    • System.in은 InputStream 타입이고, 기본적으로 바이트 단위로 읽음
    • InputStream.read()는 한 번에 1바이트만 읽기 때문에 비효율적
  2. InputStreamReader는 byte ➡️ char 변환하는 보조 스트림
    • InputStream이 byte 단위로 입력을 받은 뒤, InputStreamReader가 문자 단위 데이터로 변환  
    • 문자 단위 입력 ➡️ 출력
  3. BufferedReader는 버퍼링된 문자 입력 처리
    • BufferedReader는 내부에 버퍼(기본 8KB)를 두고 여러 글자를 한 번에 읽어들임

입출력 방법에 따른 속도 비교

방법 속도 추천 용도

System.in.read() 매우 느림 거의 안 씀
Scanner 느림 간단한 테스트
BufferedReader 빠름 일반적인 입력
BufferedReader + StringTokenizer 더 빠름 숫자/토큰 빠르게 처리
FastReader 직접 구현 최적 알고리즘, 대량 입력
파일 입력 리디렉션 빠름 테스트 자동화, 성능 실험

Java NIO

  • Stream은 blocking방식과 Non-Buffer의 특징으로 인해 입출력 속도가 느림
  • 자바 4부터 새로운 입출력(New Input/Output)이라는 뜻에서 java.nio 패키지가 포함
  • NIO는 Non-blocking방식으로 데이터를 처리 할 수 있어서 과도한 스레드 생성을 피하고 스레드를 효과적으로 재사용
  • 자바 7에서 IO와 NIO 사이의 일관성 없는 클래스 설계를 바로 잡고 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가
  • NIO.2는 java.nio의 하위 패키지(java.nio.channels, java.nio.charset, java.nio.file)에 통합

채널 (Channel)

  • 데이터가 흘러다니는 양방향의 통로
  • Stream과 다르게 양방향이기 때문에 input/output을 구분하지 않음
  • 기본적으로 Buffer를 통해서만 read와 write를 할 수 있는 buffer방식
  • blocking방식과 non-blocking방식 모두 가능

Java IO와 NIO의 차이점

구분 IO NIO

입출력 방식 Stream Channel
버퍼 방식 non-Buffer Buffer
비동기 방식 지원 x 지원 o
블로킹/논블로킹 방식 블로킹 블로킹/논블로킹

Blocking vs Non-Blocking

  • java IO
    • 입력 스트림의 read(), 출력 스트림의 write() 를 호출하면 데이터가 입력되기 전까지 쓰레드는 Blocking됨
    • 스레드가 블로킹 되면 다른 일을 할 수 없음
    • 블로킹을 빠져나오기 위해 인터럽트 하는 것도 불가능
    • 블로킹을 빠져나오는 방법은 스트림을 닫는 것
  • java NIO
    • Blocking: 스레드를 인터럽트 함으로써 빠져나올 수가 있음
    • Non-Bloking: 입출력 작업 준비가 완료된 채널만 선택해서 작업 쓰레드가 처리하므로 작업 쓰레드가 블로킹되지 않음