Java - IO & NIO ( Blocking vs Non-Blocking )


I/O 란?

  • Java에서 I/O란 입력과 출력을 말한다.
  • File I/O 가 있을 수 있고, 문서 I/O가 있을 수 있고, 네트워크 I/O가 있을 수 있다.



Byte 기반 I/O ( 바이트 스트림 )

  • InputStream 과 OutputStream 클래스를 활용한다.
  • FileInputStream, FileOutputStream, ByteArrayInputStream, ByteArrayOutputStream 등이 대표적인 클래스이다.
  • 바이트 스트림은 모든 종류의 데이터를 처리할 수 있으며, 이미지 동영상 텍스트 등 모든 종류의 파일을 다룰 수 있다.
      try (InputStream in = new FileInputStream("source.txt");
           OutputStream out = new FileOutputStream("destination.txt")) {
          int byteRead;
          while ((byteRead = in.read()) != -1) {
              out.write(byteRead);
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
    



Char 기반 I/O ( 문자 스트림 )

  • Reader 및 Writer 클래스들은 문자 단위로 데이터를 읽고 쓰는데 사용된다.
  • FileReader, FileWriter, BufferedReader, BufferedWriter 등이 대표적인 클래스이다.
  • 문자 스트림은 텍스트 데이터를 읽고 쓰기에 적합하며, 문자 인코딩을 자동으로 처리한다.
      try (Reader reader = new FileReader("source.txt");
           Writer writer = new FileWriter("destination.txt")) {
          int charRead;
          while ((charRead = reader.read()) != -1) {
              writer.write(charRead);
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
    



바이트 스트림과 문자 스트림의 공통점과 차이점

  • 텍스트 데이터를 다룰 때는 주로 문자 스트림을 사용하는 것이 더 편리하다.
  • 바이트 스트림은 모든 종류의 데이터를 다룰 수 있지만, 문자 데이터 처리 시 문자 인코딩을 직접 다루어야 한다.
  • 입출력 작업은 항상 예외 처리가 필요하므로 try-with-resources 구문을 활용하여 자원을 자동으로 닫아주는 것이 좋다.



Blocking I/O 와 Non-blocking I/O(NIO)

  • 기존 자바는 항상 Blocking I/O 방식이었다. 그러나 자바4에 NIO가 추가되었고, 자바7에서 강화된 NIO2가 추가되었다.
  • IO는 입출력 작업중 스레드가 블럭 처리가 되서 한 번의 하나의 작업만 처리된다. 그 때 동안 해당 스레드는 Block 상태가 되어 대기 상태가 된다.
    • 이 때 시스템 콜과 인터럽트 같은 과정이 일어나게 되는데, IO 동작에 접근하는 스레드가 많을 수록 Block이 많이 일어나 성능에 문제가 생길 수 있다.
    • 또한 Blocking 상태에서는 interrupt() 메서드로 스레드를 중지할 수도 없다. 오로지 스트림을 닫아야지만 빠져나올 수 있다.
    • 즉, 입력스트림의 read()를 호출하면, Blocking 상태가 되므로, 데이터가 입력되기 전까지는 스트림을 닫는 방법외에는 빠져나올 수 없다.
  • NIO는 입출력 작업 중 스레드가 Block되지 않고 계속 다른 작업을 수행할 수 있게 해준다.
    • 스레드를 Interrupt 함으로써 빠져나올 수 있다.
    • 입출력 작업 준비가 완료된 채널만 선택해서 스레드가 처리하기 때문에 블로킹 되지 않는다.
    • 과도한 스레드 생성을 피하며, 스레드를 효과적으로 재사용할 수 있고, 성능이 좋다.


어떠한 경우에 IO를 쓰고, 어떠한 경우에 NIO를 써야할까?

  • NIO는 연결 클라이언트 수가 많고, 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하는것이 좋다
    • 입출력 처리가 오래걸리면, 대기하는 작업의 수가 늘어나서 제한된 스레드로 처리하는 것이 불편할 수 있다.
  • IO는 연결 클라이언트 수가 적고 전송되는 데이터가 대용량이면서 순차적으로 처리될 필요성이 있는 경우 구현하는 것이 좋다.


IO

  • 입출력 : 스트림 방식
  • 버퍼 : Non-Buffer
  • 비동기 : 지원 x
  • Blocking 방식만 지원


NIO2 ( NIO는 구현이 어려워서, 더 쉽게 할 수 있고 성능도 향상된 API )

  • 입출력 : 채널
  • 버퍼 : Buffer
  • 비동기 : 지원 o
  • Blocking , Non-Blocking 모두 지원


스트림과 채널이란 뭘까?

  • 스트림은 데이터를 읽거나 쓰는 연속적인 데이터 흐름을 이야기한다. 순차적으로 처리된다.
    • Reader Writer 가 나눠져 있는 것 처럼, 입력과 출력 스트림은 순차적으로 각각 데이터를 읽고 쓰는데 사용된다.
  • 채널은 입출력 연산을 수행하는 양방향 통신 경로이다. 그래서 비동기적인 입출력 처리가 가능하다.
    • 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.


Buffer 와 Non-Buffer 는 뭘까?

  • IO에서 출력 스트림이 1바이트를 쓰면, 입력 스트림이 1바이트를 읽는다.
    • 이렇게 버퍼라는 중간 저장 공간 없이, 다이렉트로 입출력 작업을 처리한다.
    • 그래서 IO는 Non-Buffer 이다.
    • 그래서 성능을 향상시키기 위해 BufferedInputStream , BufferedWriter 등의 도움을 받는다.
    • 실시간성이 중요할 떄는 버퍼를 사용하지 않는 것이 좋다.
  • NIO는 기본적으로 버퍼를 사용하여 입출력을 해준다.
    • 채널은 버퍼에 저장된 데이터를 출력하고, 입력된 데이터를 버퍼에 저장한다.
    • 그래서 버퍼 방식이고, 더 성능이 좋다.
    • 실시간성보다는 성능이 중요할 때 버퍼를 사용하면 좋다.


번외 : 직렬화, 역직렬화

  • 자바에서 I/O 할 떄는 자바의 Object나 Data 등을 다른 컴퓨터 시스템에서도 사용할 수 있도록, 바이트 스트림 형태로 변환해주어야 한다.
  • 그 때 사용하는 것이 Serializable .. 직렬화이다.
  • 반대로 바이트 스트림 형태를 자바에서 읽을 수 있는 Object 형태로 다시 바꾸는걸 Deserialize .. 역직렬화 라고 한다.
  • Java에서 인터페이스로 잘 되어있기 때문에 implements 로 구현하여 사용하면 된다.






results matching ""

    No results matching ""