ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 13주차 과제: I/O
    Programming/Java live study 2021. 2. 14. 14:51
     

    13주차 과제: I/O · Issue #13 · whiteship/live-study

    목표 자바의 Input과 Ontput에 대해 학습하세요. 학습할 것 (필수) 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O InputStream과 OutputStream Byte와 Character 스트림 표준 스트림 (System.in, System.out, System

    github.com

    목표

    자바의 Input과 Output에 대해 학습하세요.

    학습할 것

     - 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

     - InputStream과 OutputStream

     - Byte와 Character 스트림

     - 표준 스트림 (System.in, System.out, System.err)

     - 파일 읽고 쓰기


    1. 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

    1.1 Stream 

    단방향으로 데이터가 연속적으로 흘러간다. 단방향이기 때문에 입력과 출력을 동시에 처리할 수 없다. 또한 Queue와 같은 선입선출(FIFO) 구조이며 애플리케이션을 기준으로 출발지인지 도착지인지에 따라 스트림의 종류가 다르다.

     

    출발지 - 출력 스트림 (OutputStream)

    도착지 - 입력 스트림 (InputStream)

     

    스트림 구조

    Stream은 FIFO(First In First Out)의 구조로 되어 있다. 먼저 보낸 데이터는 먼저 받게 된다.

     

    Java에서는 사용목적에 따라 java.oi 패키지에서 byte 단위로 입출력을 지원하는 InputStream과 OutputStream, 문자 단위로 입출력을 지원하는 Reader와 Writer를 추상 클래스로 지원하고 있다.

     

    1.2 버퍼 (Buffer)

    참고:

    github.com/kyu9/WS_study/blob/master/week13.md

    www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242

    github.com/mongzza/java-study/blob/main/study/13%EC%A3%BC%EC%B0%A8.md

     

    Buffer란 데이터를 주고 받는 장치에서 고속의 장치와 저속의 장치 간의 속도 차이를 줄여주기 위해 데이터를 임시 저장하는 공간을 의미한다.

     

    간단한 예시를 들어보면, 만약 물을 먹기 위한 텀블러가 있다고 가정한다. 물을 마시기 위해 주방으로 향하는데, 한 번에 한입씩 떠와서 먹는 것과, 한 컵씩 떠와서 먹는 상황을 비교해보면 쉽게 알 수 있다. 컵이라는 buffer를 활용하여 한 번에 한 컵씩 물을 떠오게 되면 주방까지의 접근 횟수를 크게 줄일 수 있다.

     

    접근 횟수를 줄인다는 의미는 결국 OS 레벨에 있는 input/out System call의 횟수 자체를 줄이게 된다. 

     

    buffer의 유무

    정리하면, 전달하는 데이터를 Buffer의 크기만큼 보관하고, 한번에 전송하는 것이다. 만약 데이터가 Buffer보다 크다면 나머지 데이터는 대기한다. 물론 Buffer의 데이터를 강제로 내보낼 수도 있다 (flush). 장점으로는 여러번 데이터를 전송하는 것이 아니라 한 번에 많이 보내기 때문에 데이터 전송을 위한 비용이 적어진다. 

     

    이러한 Buffer는 여러 분야에서 사용하고 있다. 예를들면 PC에 보조 기억 장치와 주 기억 장치의 속도 차이를 매꾸기 위해 중간중간 Buffer를 두어 데이터 전송에 필요한 시간을 단축시킨다.

     

    Java에서는 Buffer를 사용하기 위해서 BufferedOutputStream과 BufferedInputStream, BufferedReader, BufferedWriter를 사용하여 버퍼링할 수 있다.

     

    Java에서는 또한 buffer가 사용하는 메모리의 위치에 따라서 non-direct buffer와 direct buffer로 분류할 수 있다.

     

     - non-direct buffer는 JVM이 관리하는 heap 메모리 공간을 이용하는 buffer이다.

     - direct buffer는 운영체제가 관리하는 메모리 공간을 이용한 buffer이다.

     

      non-direct buffer direct buffer
    사용하는 메모리 공간 JVM의 heap 메모리 운영체제의 메모리
    버퍼 생성 시간 빠르다 느리다
    버퍼의 크기 작다 크다
    입출력 성능 낮다 높다

     

    direct buffer는 좋은 성능을 가지고 있지만 운영체제의 메모리를 할당 받아야 하기 때문에 네이티브 함수를 호출해야 하는 등 속도가 느리다. 한 번만 생성하여 재사용하는 것이 효율적이다.

     

    non-direct buffer 생성을 위해서는 각 buffer 클래스의 정적 메소드 allocate(), wrap() 메소드를 호출한다.

    direct buffer 생성을 위해서는 ByteBuffer 클래스의 정적 메소드 allocateDirect() 메소드를 호출하여 생성이 가능하다. 또한 다른 타임의 direct buffer 생성을 위해서는 asCharBuffer(), asIntBuffer() 등의 메소드를 사용한다.

    1.3 Channel

    채널은 양방향으로 데이터 전송이 가능하다. 즉 채널을 통해서 읽기 및 쓰기가 가능하다. 또한 비동기적으로 사용할 수 있고, 항상 buffer를 활용해야 한다.

    1.4 NIO (New Input/Output)

    기존의 IO의 단점을 개선하기 위해 Java 4 부터 추가된 패키지이다.

     

    Java nio 패키지

    NIO 패키지 및 하위 패키지 내용
    java.nio 다양한 버퍼 클래스가 들어 있다.
    java.nio.channels 채널 관련 클래스가 들어 있다.
    java.nio.channels.spi channels 패키지를 위한 서비스 제공자 클래스가 들어 있다.
    java.nio.charset 문자셋, 인코더, 이코더 관련 API가 들어 있다.
    java.nio.charset.spi charset 패키지를 위한 서비스 제공자 클래스가 들어 있다.
    java.nio.file 파일과 피일 시스템 접근을 위한 클래스가 들어 있다.
    java.nio.file.spi file 패키지를 위한 서비스 제공자 클래스가 들어 있다.

     

    IO와 NIO는 데이터를 입출력하는 목적은 동일하지만 방식에서 차이가 존재한다.

     

    방식 IO NIO
    입출력 방식 스트림 방식 채널 방식
    버퍼 방식 non-buffer buffer
    비동기 방식 지원하지 않는다. 지원한다.
    blocking/non-blocking 방식 blocking 방식만 지원한다. (동기 방식) blocking과 non-blocking 모두 지원한다. (동기/비동기 방식)

    1.5 IO의 non-buffer, NIO의 buffer

    IO는 스트림 방식을 사용하고 있고, 추가적인 buffer를 제공하지 않는다. buffer 사용을 위해선 추가적인 스트림인 BufferedInputStream과 BufferedOutputStream이 필요하다. 하지만 NIO의  경우 채널 방식이고 buffer를 제공하고 있기 때문에 입출력시 IO보다 높은 성능을 가진다.

     

    IO는 입력된 데이터를 별도로 관리하지 않으면 해당 데이터를 자유롭게 사용할 수 없다. 하지만 NIO의 경우 buffer 내의 데이터의 위치를 이동해가면서 필요한 부분만 읽고 쓸 수 있다.

     

    IO와 NIO

    1.6 blocking, non-blocking

    IO는 blocking이 가능하다. 입출력 스트림의 read(), write() 메소드를 호출하면 데이터가 입출력되기 전까지 Thread는 blocking 상태가 된다. 

     

    IO Thread가 blocking 상태가 되면 다른 일을 할 수 없고, blocking 상태를 빠져나오기 위한 interrupt 또한 불가능하다. blocking 상태를 탈출하는 방법은 stream을 닫는 것이다.

     

    NIO는 blocking, non-blocking의 특징을 모두 가진다. 즉 Thread를 interrupt할 수 있다. NIO의 non-blocking은 입출력 작업 준비가 완료된 채널만 선택하여 작업 Thread가 처리하기 때문에 작업 Thread가 blocking 되지 않는다. 

     

    NIO의 non-blocking은 멀티플렉서 역할을 하는 Selector 객체를 사용한다. Selector는 복수 개의 채널 중 준비 완료된 채널을 선택하는 방법을 제공한다.


    2. InputStream과 OutputStream

    2.1 InputStream

    참고: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html

     

    Java의 기본 입력 스트림 클래스인 java.io.InputStream이다. 이 클래스는 모든 입력 스트림 클래스의 부모 클래스이다.

     

    InputStream 추상 클래스
    다양한 구현체들을 가지고 있다.

    위 subclass들은 InputStream을 상속한 구현체들이다. InputStream에 정의된 많은 메소드들을 재정의하고 있다.

     

    메소드 설명
    public abstract int read() 입력 스트림에서 1byte를 읽어서 리턴한다. 이 메소드가 리턴하는 문자의 값의 범위는 0 ~ 255이다. 하지만 입력 스트림의 끝에서 더 이상 읽을 문자가 없다면, -1을 리턴한다. read 메소드가 리턴하는 값의 범위는 -1 ~ 255이기 때문에 byte 타입으로 표현이 불가능하다. 그래서 return 하는 타입이 int이다.
    public long skip(long n) 입력 스트림에서 앞으로 읽을 n개의 문자를 건너뛰고, 그 다음 부터 읽는다.
    public int available() 이미 입력 스트림에 도착해서 입력 버퍼에 들어있다면 즉시 읽을 수 있는 데이터의 크기를 리턴한다. 입력 버퍼가 비어있다면 0을 리턴한다.
    public void close() 이 입력 스트림을 닫고 스트림과 관련된 모든 시스템 리소스를 해제한다.

    2.2 OutputStream

    참고: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/OutputStream.html

     

    Java의 기본 출력 스트림 클래스는 java.io.OutputStream이다. 이 클래스는 모든 출력 클래스의 부모 클래스이다.

     

    OutputStream 추상 클래스
    다양한 구현체를 가지고 있다.

    InputStream과 마찬가지로 다양한 구현체들을 가지고 있다.

     

    메소드 설명
    public abstract void write(int b) 1byte를 출력 스트림에 출력한다. 메소드의 파라미터는 32bit int type이지만 이중 8bit인 1byte만 출력되고 나머지 24bit는 무시된다. 낮은 자리 (LSB)의 8bit가 출력된다.
    public void flush() 출력 데이터가 모아져 있는 버퍼의 내용을 즉시 출력한다.
    public void close() 출력 스트림을 닫는다. 닫히기 직전에 출력 버퍼의 내용이 자동으로 flush 된다.

     

    LSB, MSB

     - LSB (Least Signficant Bit): 이진수에서 가장 낮은 자리 비트

     - MSB (Most Signficant Bit): 이진수에서 가장 높은 자리 비트


    3. Byte와 Character 스트림

    3.1 Byte Stream

    byte 기반으로 데이터를 입출력하는 스트림이다. 앞서 공부한 InputStream, OutputStream의 구현체들이 이에 해당한다.

     

    파일에서 byte 단위로 문자를 읽기 위해서는 FileInputStream을 사용한다.

    파일에 byte 단위로 문자를 쓰기 위해서는 FileOutStream을 사용한다.

    기본 자료형을 읽을 때는 DataInputStream을 사용한다.

    기본 자료형을 쓸 때는 DataOutputStream을 사용한다.

     

    그 밖에도 다양한 Stream이 존재하고, 필요에 따라 적절히 조립하여 구성할 수 있다.

     

    예를 들어 파일에서 버퍼링을 통해 byte 단위로 읽기 위한 예시이다.

     

    InputStream in = new BufferedInputStream(new FileInputStream("파일 경로 및 파일명"));

     

    파일에서 byte 단위로 버퍼링하면서 쓰기 위한 예시이다.

     

    OutputStream out = new BufferedOutputStream(new FileOutputStream("파일 경로 및 파일명"));

    3.2 Character Stream

    기존에 InputStream과 OutputStream은 byte 단위 입출력을 기본으로 구현한 클래스이다. Reader와 Writer는 character 단위 입출력을 기본으로 구현된 클래스이다. Reader와 Writer 또한 추상 클래스로 구성되어 있다. 

     

    Reader와 Writer는 앞서 언급한 InputStream, OutputStream과 유사한 구조를 가지고 있다.

     

    FileReader는 파일에서 문자를 읽는다. FileWriter는 파일에 문자를 출력한다.

    BufferedReader는 버퍼링을 사용하여 문자를 읽는다. BufferedWriter는 버퍼링을 사용하여 문자를 출력한다.

     

    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
    BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));

    4. 표준 스트림 (System.in, System.out, System.err)

    참고: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html

     

    java.lang.System에는 표준 입출력을 위한 Stream들이 static 필드도 정의되어 있다. 또한 System 클래스는 private로 생성자를 제한하였기 때문에 인스턴스화 시킬 수 없다.

     

    다른 사용자가 이 클래스를 인스턴스화하지 않도록 합니다.

    유형 필드 설명
    static PrintStream err "표준"오류 출력 스트림이다.
    static InputStream in "표준"입력 스트림이다.
    static PrintStream out "표준"출력 스트림이다.

    4.1 System.in

    표준 입력 스트림이다. 이 스트림은 열려 있기 때문에 입력 데이터를 제공할 준비가 되어 있다. 일반적으로 이 스트림은 사용자가 지정한 키보드 입력 혹은 다른 입력 소스에 해당한다.

    4.2 System.out

    표준 출력 스트림이다. 이 스트림은 이미 열려 있기 때문에 출력 데이터를 받을 준비가 되어 있다. 일반적으로 디스플레이 출력 또는 사용자가 지정한 다른 출력 대상에 해당한다. 또한 PrintStream이기 때문에 기본적으로 synchronized 메소드들로 구성되어 있기 때문에 thread-safe하다.

    4.3 System.err

    표준 error 출력 스트림이다. 이 스트림 또한 이미 열려 있고, 출력 데이터를 받을 준비가 되어 있다. 일반적으로 디스플레이 출력 또는 호스트 환경이나 사용자가 지정한 다른 출력 대상에 해당한다. 사용자가 즉시 주목해야 하는 오류 메시지 또는 기타 정보를 표시하는데 사용된다. 지속적으로 모니터링 되지 않는다.


    5. 파일 읽고 쓰기

    5.1 파일 읽기

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.io.Reader;
    import java.io.Writer;
    
    public class Exam01 {
    
        public static void main(String[] args) throws IOException {
    
            Reader reader = new BufferedReader(new FileReader("c:/java/a.txt"));
            Writer writer = new BufferedWriter(new OutputStreamWriter(System.out));
    
            while (true) {
                int read = reader.read();
                if (read == -1) break;
                writer.write(read);
            }
    
            reader.close();
            writer.flush();
            writer.close();
        }
    }

     

    Reader에는 c:/java/a.txt를 연결한다. a.txt에는 간단한 두줄짜리 문장이 적혀있다. Writer에는 표준 출력 스트림인 System.out을 연결한다. 

     

    read로 값을 읽어오고, writer 버퍼에 차곡차곡 출력 데이터를 쌓는다. 더 이상 읽어드릴 데이터가 없으면 read는 -1을 반환받고, 즉시 반복문을 탈출한다.

     

    flush로 버퍼를 비워주며 출력을 확인한다.

     

    java live study
    13주차 I/O

    5.2 파일 쓰기

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    public class Exam02 {
    
        public static void main(String[] args) throws IOException {
    
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("c:/java/b.txt"));
    
            String s1 = bufferedReader.readLine();
            String s2 = bufferedReader.readLine();
    
            bufferedWriter.write(s1 + "\n"); // java live study
            bufferedWriter.write(s2); // 13주차 I/O
    
            bufferedWriter.flush();
            bufferedReader.close();
            bufferedWriter.close();
        }
    }

     

    b.txt

    정상적으로 파일에 출력된 것을 알 수 있다.

    5.3 파일 읽고 쓰기

    이번엔 a.txt를 c.txt로 복사하는 예시를 작성하였다.

     

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.Reader;
    import java.io.Writer;
    
    public class Exam03 {
    
        public static void main(String[] args) throws IOException {
    
            try (Reader reader = new BufferedReader(new FileReader("c:/java/a.txt"));
                 Writer writer = new BufferedWriter(new FileWriter("c:/java/c.txt"))) {
    
                while (true) {
                    int read = reader.read();
                    if (read == -1) break;
                    writer.write(read);
                }
            }
        }
    }

     

    Reader와 Writer는 모두 closeable 인터페이스를 구현하고 있기 때문에 앞서 배운 try-with-resources 또한 활용하였다. a.txt의 내용을 c.txt로 복사하는 코드이다.

     

    c.txt

    정상적으로 복사된 것을 확인할 수 있다. 또한 자동으로 close 메소드를 호출하기 때문에 flush를 작성하지 않아도 출력한 것을 알 수 있다.

     

    'Programming > Java live study' 카테고리의 다른 글

    14주차 과제: 제네릭  (0) 2021.02.24
    12주차 과제: 애노테이션  (0) 2021.02.03
    11주차 과제: Enum  (0) 2021.01.27
    10주차 과제: 멀티쓰레드 프로그래밍  (0) 2021.01.21
    9주차 과제: 예외 처리  (0) 2021.01.13

    댓글

Designed by Tistory.