코딩하는 털보

STUDY HALLE - 13주차 본문

Diary/Study Halle

STUDY HALLE - 13주차

이정인 2021. 2. 16. 19:49

목표

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

학습할 것 (필수)

  • 스트림, 버퍼, 채널 기반 I/O
  • I/O 스트림
  • Byte 스트림
  • Character 스트림
  • Buffered 스트림
  • java.nio
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

이번 스터디는 대체적으로 Oracle Java Tutorials 문서와 자바 관련 서적(Do it! 자바 프로그래밍 입문)을 참조하였다.

https://docs.oracle.com/javase/tutorial/essential/io/


스트림, 버퍼, 채널 기반 I/O

스트림 기반 I/O

스트림은 데이터가 이동하는 단방향통로를 의미한다. FIFO 구조를 가지고 있으며 일련적으로 밖에 접근할 수 없기 때문에 특정 위치의 데이터에만 접근 할 수 없다. 스트림을 지향하는 I/O는 스트림을 통해 한 번에 하나 이상의 바이트를 읽는다는 것을 의미한다.

Java 프로그램은 한 번에 한 바이트 씩 스트림에서 읽거나 쓴다. Java의 스트림은 비동기 I/O 방식을 지원하지 않는데, 그렇기 때문에 스레드가 현재 작업을 떠나 즉시 I/O에 대한 작업을 시작한다. 게다가 Java 스트림은 blocking I/O이기 때문에 I/O 시작부터 읽을 수있는 데이터가 있거나 데이터가 완전히 기록 될 때까지 스레드가 block 된다.

  • 단방향 통신
  • FIFO
  • 비동기 미지원
  • Blocking I/O

게시물 이미지

사진 출처 : https://medium.com/@nilasini/java-nio-non-blocking-io-vs-io-1731caa910a2

버퍼 기반 I/O

버퍼 기반 I/O의 접근 방식은 약간 다르다. 버퍼 기반 I/O에서는 데이터를 나중에 처리할 버퍼로 읽어온다. 필요에 따라 버퍼에서 앞뒤로 이동할 수도 있다. 이렇게하면 처리 중에 약간 더 많은 유연성이 제공된다.

그러나 버퍼를 완전히 처리하기 위해 필요한 모든 데이터가 버퍼에 포함되어 있는지도 확인해야한다. 또한 버퍼로 더 많은 데이터를 읽을 때 아직 처리하지 않은 버퍼의 데이터를 덮어 쓰지 않도록 할 필요가 있다.

채널 기반 I/O

채널은 Java IO의 진화격인 IO기능을 가진 패키지인 Java NIO에서 사용되는 데이터 통로이다.

채널은 스트림과 유사하지만 양방향 통신이 가능하다는 차별성이 있다. 양방향 이기 때문에 스트림과는 다르게 데이터의 읽기 쓰기가 하나의 통로에서 가능하다.

또한 blocking 방식으로 작동하던 스트림과는 달리, non-blocking 방식이 지원되어 보다 효율적으로 리소스를 사용할 수 있다.

:: NIO의 읽기 동작은 버퍼를 만든 다음 채널에 데이터를 읽어달라고 요청한다. 쓰기 동작은 버퍼를 만들고 데이터로 채운 다음 채널에 쓰기를 요청한다.

게시물 이미지

사진 출처 : https://medium.com/@nilasini/java-nio-non-blocking-io-vs-io-1731caa910a2


I/O Stream

자바 I / O 스트림은 입력 소스 또는 출력 대상을 나타낸다. 스트림은 디스크 파일, 장치, 다른 프로그램 및 메모리 배열을 포함한 다양한 종류의 소스 및 대상을 나타낼 수 있다.

내부적으로 작동하는 방식과는 관계없이 모든 스트림은 이를 사용하는 프로그램에 일관성있는 단순 모델을 제공한다.

IO 스트림의 구분

  • IO 대상 기준 : 입력 스트림, 출력 스트림
  • 자료의 종류 : Byte 스트림, Character 스트림
  • 스트림의 기능 : 기반 스트림, 보조 스트림

입력 or 출력 스트림
입력 스트림 : 대상으로 부터 자료를 읽어 들이는 스트림
ex) FileInputStream(바이트), FileReader(문자), BufferedInputStream(보조), BufferedReader, etc
출력 스트림 : 대상으로 자료를 출력하는 스트림
ex) FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter, etc

바이트 or 문자 단위 스트림
바이트 단위 스트림 : 바이트 단위로 자료를 읽고 씀 (동영상, 음악 등)
ex) ~Stream
문자 단위 스트림 : 문자는 2byte이상씩 처리해야 함
ex) ~Reader, ~Writer

기반 or 보조 스트림
기반 스트림 : 대상에 직접 자료를 읽고 쓰는 기능의 스트림
ex) File~
보조 스트림 : 직접 읽고 쓰는 기능은 없고 추가적인 기능을 제공해 주는 스트림
기반 스트림이나 또 다른 보조 스트림을 생성자의 매개변수로 포함한다
ex) Buffered~, InputStreamReader, OutputStreamWriter


Byte 스트림

바이트 스트림 을 사용 하여 8 비트 바이트의 입력 및 출력을 수행할 수 있다. 모든 바이트 스트림 클래스는 InputStream및 OutputStream를 상속받는다.

참고로 InputStream을 상속받아 정의하는 하위 클래스는 항상 다음 입력 바이트를 반환하는 메소드(int read())를 제공해야하고, OutputStream을 상속받아 정의하는 하위 클래스는 항상 최소한 1 바이트의 출력을 작성하는 메소드(void write(int b))를 제공해야한다.

대표적으로 FIle I/O에 특화된 바이트 스트림인 FileInputStream와 FileOutputStream이 있다.

FileInputStream / FileOutputStream

package javaIO;

import java.io.*;

public class StreamTest {
    public static void main(String[] args) throws IOException {

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("test.txt");
            out = new FileOutputStream("outagain.txt");
            int c;

            //read()메소드는 스트림의 끝에 도달하여 사용가능한 바이트가 없는 경우에 '-1'을 리턴한다.
            while ((c = in.read()) != -1) {
                out.write(c);
            }
        //스트림을 닫는것은 매우 중요하다. 예외의 발생 여부에 관련없이 닫기 위해서 되도록 finally 블록에 위치하게 된다.
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

바이트 스트림은 한번에 한 바이트씩 읽고 쓴다.

Simple byte stream input and output.

이미지 출처 : https://docs.oracle.com/javase/tutorial/essential/io/bytestreams.html


Character 스트림

자바에서의 문자 저장은 내부적으로 유니코드 규칙을 사용하는데, 문자 스트림을 사용하면 소스의 character-set을 유니코드로 자동으로 변환한다. 모든 문자 스트림 클래스는 Reader 및 Writer 클래스를 상속받는다.

문자 단위의 변환 처리를 해야하니 속도는 바이트 스트림이 빠르지만 한글과 같이 2byte 이상으로 이루어진 문자는 바이트 스트림으로 다루기 어렵기 때문에 문자 스트림을 사용하면 편리하다.

package javaIO;

import java.io.*;

public class StreamTest {
    public static void main(String[] args) throws IOException {

        FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("test.txt");
            outputStream = new FileWriter("characteroutput.txt");

            //스트림을 읽고쓰는데에 int를 사용하는 것은 바이트 스트림과 같다.
            //그러나 바이트 스트림은 int에 8비트 바이트를 담는 한편, 문자 스트림은 int에 16비트 문자 값을 담고 있다.
            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

내부적으로 문자 스트림은 바이트 스트림을 사용한다. 예를들어 FileReader는 FileInputStream을 사용하여 물리적인 I/O를 수행한 뒤 바이트와 문자간의 변환을 처리한다.

byte를 문자로 변환하는 목적으로 사용되는 보조 문자 스트림으로는 InputStreamReader 와OutputStreamWriter가 있다.

public class SystemInTest {
        public static void main(String[] args) {

            System.out.println("입력 후 '끝' 이라고 쓰세요.");

            try {
                int i;
                InputStreamReader isr = new InputStreamReader(System.in); //byte -> 문자, 다른 스트림을 생성자의 매개변수로 받음
                while ((i = isr.read()) != '끝') {
                    System.out.print((char)i);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}

Buffered 스트림

자바 I/O Stream은 기본적으로 unbuffered I/O 이다. 즉, 읽기 또는 쓰기 요청을 하는 동안 리소스를 직접 처리하여 종종 디스크 액세스, 네트워크 활동 또는 상대적으로 비용이 많이 드는 작업이 포함될 수 있기 때문에 프로그램의 효율성이 훨씬 떨어질 수 있다.

이러한 종류의 오버헤드를 줄이기 위해 Java는 버퍼가 가득 차거나(입력 읽기) 비어있을 때(출력에 쓰기)만 native API를 요청하는 방식의 Buffered I / O 스트림을 구현하였다. 이렇게하면 API에 대한 불필요한 요청이 심각하게 줄어들고 간격을 두고 한 번에 모두 실행되도록 하기 때문에 리소스를 좀 더 놓아줄 수 있다.

우리는 unbuffered 스트림을 래핑(buffered 스트림 생성자의 매개변수로 unbuffered 스트림 입력)하여 buffered 스트림으로 변환할 수 있다.

inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

BufferedInputStream 및 BufferedOutputStream은 Buffered 바이트 스트림을 생성하고 BufferedReader 및 BufferedWriter는 Buffered 문자 스트림을 생성한다. 이 네가지 Buffered 스트림의 버퍼 사이즈는 기본적으로 8192 bytes이지만 생성자를 통해 변경할 수 있다.

package javaIO;

import java.io.*;

public class StreamTest {
    public static void main(String[] args) throws IOException {

        BufferedReader inputStream = null;
        BufferedWriter outputStream = null;


        try {
            inputStream = new BufferedReader(new FileReader("test.txt"));
            outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

참고 : https://medium.com/@ericgithinji/why-we-use-buffered-i-o-streams-in-java-and-some-other-languages-too-dde784fc5d70


java.nio

java.nio는 java 4(NIO)버전부터 나와 7(NIO2)버전에서 한번더 업그레이드 된 I/O관련 신기능 패키지이다.

  • NIO는 기존 Java의 I/O 방식이(이후로 그냥 IO라함) 스트림을 이용하여 단방향 I/O를 실행했던 것과 비교적으로 채널이라는 양방향 통로를 이용하여 외부 데이터와 통신한다. 즉, NIO는 IO와 달리 읽기/쓰기를 하나의 통로로 해결할 수 있습니다.

  • 또한 IO가 기본적으로는 unbuffered I/O 였었다는 것(보조 스트림을 이용하여 버퍼링 할 수 는 있음.)과 비교적으로 NIO는 기본적으로 Buffer를 이용하여 빠른 속도를 지원한다.

  • NIO는 IO와 다르게 비동기 방식을 제공한다. (메소드 호출 시점과 I/O 수행 시점이 다를 수 있음.)

  • NIO는 IO와 다르게 non-blocking을 제공한다. (I/O를 수행하는 동안 스레드가 block 되지 않음.), 참고로 FileChannel 클래스는 non-blocking이 지원되지 않아 대용량 입출력 위해서는 다중 스레드를 이용해야 한다.

참고 : https://medium.com/@nilasini/java-nio-non-blocking-io-vs-io-1731caa910a2


표준 입출력 스트림(System.in, System.out, System.err)

JAVA에서는 스크린과 키보드를 통한 입출력 방법인 표준 입출력을 제공한다. 표준 입출력을 제공하는 클래스는 java.lang.System으로 멤버 변수인 in, out, err을 이용해서 표준 입력, 표준 출력, 표준 에러를 제공한다.

출처: https://hyeonstorage.tistory.com/235 [개발이 하고 싶어요]

public Class System {   
    public final static InputStream in = null;  //표준 입력 스트림
    public final static PrintStream out = null; //표준 출력 스트림
    public final static PrintStream err = null; //표준 에러 스트림
  ...
}

public final static InputStream in : 표준 입력 스트림, 일반적으로 이 스트림은 호스트 환경 또는 사용자가 지정한 키보드 입력 또는 다른 입력 소스에 해당한다.

System.in은 InputStream 형태로 지정되어 있다. System 클래스는 자바 버추얼 머신을 구성하고 있는 표준 장치를 뜻하는 클래스이다. 자바 버추얼 머신은 그 자체가 완벽한 하나의 컴퓨터 플랫폼을 가정하고 있기 때문에 독립적으로 동작할 수 있는 구조를 표현하기 위하여 표준 입력과 표준 출력을 스스로의 System 클래스에 등록하여 사용한다.

여기에서 주목해야 할 부분은 System.in 변수의 타입이 InputStream 이라는 점이다.

InputStream 클래스는 최상위 클래스이면서 추상 클래스이다. 따라서 InputStream은 객체를 생성할 수 없는 클래스이다. 그런데도 System.in은 실제로 객체가 존재하고 있으며 이를 통하여 키보드 입력을 받을 수 있다. 이것은 변수의 타입은 선조 클래스이지만 실제 객체는 후손 객체이다. 자연스러운 형 변환이 지원되기 때문에 가능하다.

System.in을 통하여 접근되는 객체는 자바 버추얼 머신이 메모리로 올라오면서 미리 객체를 생성해 놓는, 대표적인 객체이다. 자료형이 InputStream이기 때문에 바이트 단위로만 입출력이 허용된다.

키보드에서 입력하는 자료는 때에 따라서 두 바이트가 합쳐져야 의미를 가지는 경우가 있다. 그래서 System.in을 통하여 읽을 경우에는 영문과 한글의 처리를 분리해서 구성해야 제대로 인식할 수 있다.

public final static PrintStream out : 표준 출력 스트림, 이 스트림은 이미 열려 있고 출력 데이터를 받을 준비가 되어있다. 일반적으로 이 스트림은 디스플레이 출력 또는 호스트 환경이나 사용자가 지정한 다른 출력에 해당한다.

System.out 변수는 표준 출력 장치 객체를 가리키는 대표적인 출력 변수이다. 우리가 자주 사용하던 System.out.println(); , 표준 출력 스트림을 사용한 것인데, PrintStream 객체의 println() 메소드를 이용했던 것이었던 것!

System.out은 PrintStream 타입으로 선언되어 있는데 PrintStream은 OutputStream 클래스의 후손 클래스로 Exception을 안전하게 처리한 메소드로만 구성되어 있다. 이런 이유로 System.out 을 이용하여 출력할 때는 try, catch 구문을 작성할 필요가 없다.

public final static PrintStream err : 표준 에러 스트림, 일반적으로 이 스트림은 사용자가 즉시 주목해야하는 오류 메시지 또는 기타 정보를 표시하는 데 사용된다.

System.err 객체는 표준 에러 출력 장치를 의미하는데 일반적으로 System.out과 마찬가지로 모니터로 지정되는 경우가 많다. 일반적인 정상 출력은 System.out으로 나가고, 오류가 발생할 때 알려주어야 할 내용은 System.err로 나간다고 볼 수 있다. 이 변수의 타입도 PrintStream 클래스 타입으로 System.out을 사용하는 방법과 동일하다.

출처: https://hyeonstorage.tistory.com/235 [개발이 하고 싶어요]


파일 읽고 쓰기

파일 읽기

파일 읽고 받은 문자 표준 출력

package javaIO;

import java.io.*;

public class ReadFile {
    public static void main(String[] args) throws IOException {

        BufferedReader inputStream = null;


        try {
            inputStream = new BufferedReader(new FileReader("test.txt"));

            int c;
            while ((c = inputStream.read()) != -1) {
                System.out.print((char)c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

success

abcdef
Process finished with exit code 0

파일 쓰기

표준 입력으로 받은 문자를 파일로 출력

package javaIO;

import java.io.*;

public class ReadFile {
    public static void main(String[] args) throws IOException {

        BufferedWriter outputStream = null;
        InputStreamReader isr = null;

        try {
            int i;
            isr = new InputStreamReader(System.in);
            outputStream = new BufferedWriter(new FileWriter("new.txt"));

            while ((i = isr.read()) != '끝') {
                outputStream.write(i);
            }

        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            if (isr != null) {
                isr.close();
            }
        }
    }
}

success

> cat new.txt 
date
Comments