자바 IO에 대해서 알아보겠습니다

IO에 대해 깊은 내용을 이해하기 보다 프로그램에서 입출력을 받을때 어떻게 동작하는지 전체적인 큰 그림을 그리는데에 집중해보았습니다.

학습 목표

  • 스트림 (Stream)기반의 IO
  • Byte와 Character 스트림
  • Scanner 와 BufferedReader 차이는?

스트림 (Stream)기반의 IO

IO란? input output으로 프로그램 내에서 입력과 출력을 의미합니다.

입력과 출력은 자주 사용됩니다.

데이터들은 사용자로부터 입출력, 파일로부터 입출력, 네트워크로부터 입출력과 같이 여러 상황에서 발생할 수 있습니다.

자바 입출력은 프로그램을 기준으로 데이터 입출력이 되는 것을 명심해야 합니다.

Untitled

스트림 기반의 IO

자바에서는 데이터를 외부에서 읽고 다시 외부로 출력하는 작업(입출력)에 스트림(Stream)을 사용합니다.

스트림은 단일 방향으로 연속적으로 흘러가는 것을 말하며, 데이터는 출발지에서 나와 도착지로 들어간다는 개념입니다

  • 스트리밍의 특성이 단방향이므로 하나의 스트림으로 입력과 출력을 모두 할 수 없음(단일통신)
  • 프로그램이 네트워크상의 다른 프로그램과 데이터 교환을 하기 위해서는 양쪽 모두 입력 스트림과 출력 스트림이 따로 필요

  • 입출력 작업의 예: 키보드, 파일, 네트워크에서 입력과 모니터, 파일, 네트워크의 출력

프로그램이 출발지냐 또는 도착지냐에 따라 스트림의 종류가 결정

- 프로그램이 데이터를 입력받을 때는 입력 스트림(InputStream)

- 프로그램이 데이터를 보낼 때는 출력 스트림(OutputStream)

Untitled

Byte와 Character 스트림

스트림의 종류에 대한 설명

바이트 기반 스트림: 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 주고 받을 수 있음

문자 기반 스트림: 오직 문자만 주고 받도록 특화

byte 스트림 - inputstream, outputstream

InputStream(java.io 공식문서 참고)

· 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스

· 모든 바이트 기반 입력 스트림은 이 클래스를 상속

ex) FileInputStream, BufferedInputStream, DataInputStream 클래스

inputstream 클래스에 정의된 메소드

Method Summary

read()

: 입력 스트림으로부터 1바이트를 읽고 4바이트 int 타입으로 리턴.

=> 리턴된 4바이트 중 1바이트에만 데이터가 들어 있음.

read(byte[] b) 메서드

: 입력 스트림으로부터 매개값으로 주어진 바이트 배열의 길이만큼 바이트를 읽고 저장.

읽은 바이트 수를 리턴.

배열의 길이보다 실제 읽은 바이트 수가 적으면 읽은 수 만큼만 리턴.

이 메서드도 역시 입력 스트림으로부터 더 이상 바이트를 읽을 수 없으면 read()는 -1 리턴

=> 읽을 수 있는 마지막 바이트까지 루프를 돌며 한 바이트씩 읽을 수 있음.

close메서드

현재 열려있는 InputStream을 닫는다

InputStream을 더 이상 사용하지 않을 경우 호출하여 InputStream에서 사용했던 시스템 자원을 풀어줌

closeable, autocloseable 을 상속받은 클래스들은 무조건 close를 해줘야한다 왜냐하면 해당 클래스가 구현된 것은 GC가 메모리처리를 해주지 않기 때문이다.


질문

inputStream 으로 읽어들인 데이터 1, 2, 3을 System.out.println(); 으로 출력해보았을 경우 왜 49, 10, 50, 10, 51 이 출력될까요 ?

CRLF

답변

출력시 아스키코드로 표현이 됨

읽어들인 데이터가 각각 1, 2, 3일때 이를 System.out.println(); 으로 출력해보면 49, 13, 10, 50, 13, 10, 51 이 출력된 것을 볼 수있음 (10은 줄바꿈 Line Feed(LF), 13은 커서 가장 좌측 이동 Carriage Return(CR))

이유는 해당 숫자가 아스키코드로 변환되었기 때문


OutputStream(java.io 공식문서 참고)

·  바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스

·  모든 바이트 기반 출력 스트림 클래스는 이 클래스를 상속받아 만듦

ex) FileOutputStream, PrintStream, BufferedOutputStream, DataOutputStream 클래스

outputstream 클래스에 정의된 메소드

method summary

write(int b)메소드

·  매개 변수로 주어진 int 값에서 끝에 있는 1바이트만 출력 스트림으로 보내는 메소드

주의! int 타입이지만, 4바이트 모두를 보내는 것으로 오해하지 말자

write(byte[] b)메소드

·  매개값으로 주어진 바이트 배열의 모든 바이트를 출력 스트림으로 보내는 메소드

close 메소드

OutputStream을 더 이상 사용하지 않을 경우 close() 메소드를 호출해서 OutputStream에서 사용했던 시스템 자원을 풀어줌


character 스트림 - reader writer

Reader(공식문서 BufferedReader)

· 문자 기반 입력 스트림의 최상위 클래스로 추상 클래스

모든 문자 기반 입력 스트림은 이 클래스를 상속받아서 만들어진다. FileReader. BufferedReader, InputStreamReader 클래스 등이  Reader 클래스를 상속하고 있다.

Reader 클래스에 정의된 메소드

method summary

read()

· 입력 스트림으로부터 한 개의 문자(2바이트)를 읽고 4바이트 int 타입으로 리턴

· 리턴된 4바이트 중 끝에 있는 2바이트에 문자 데이터가 들어 있음

read(char[] cbuf) 메소드

· 입력 스트림으로부터 매개값으로 주어진 문자 배열의 길이만큼 문자를 읽고 배열에 저장

· 읽은 문자 수를 리턴

ex) 입력 스트림에 세 개의 문자가 들어오면, 길이가 2인 문자 배열로 두 번 만에 읽을 수 있음

·  read() 메소드와 같이 입력 스트림으로부터 문자를 더 이상 읽을 수 없다면 -1을 리턴

close() 메소드

· Reader를 더 이상 사용하지 않을 경우 close() 메소드를 호출해서 Reader에서 사용했던 시스템 자원을 풀어줌


Writer(공식문서 BufferedReader)

· 문자 기반 출력 스트림의 최상위 클래스로 추상 클래스

· 모든 문자 기반 출력 스트림 클래스는 이 클래스를 상속받아 만들어짐

ex) FileWriter, BufferedWriter, PrintWriter, OutputStreamWriter 클래스


Writer 클래스에 정의된 메소드

method summary

write(int c)

· 매개 변수로 주어진 int 값에서 끝에 있는 2바이트 (한 개의 문자)만 출력 스트림으로 보냄


질문

write(int c) 메서드에서 Character 스트림은 문자를 받는데 왜 int 타입을 입력 받을까요 ?

답변

클래스 문자 또한 숫자 int 타입으로 표현이 가능하므로 아스키코드표 참고


write(char[] cbuf)

·  매개값으로 주어진 char[] 배열의 모든 문자를 출력 스트림으로 보냄

표준 스트림 - system.in system.out

System.in

· System 클래스의 in 정적 필드: 프로그램이 콘솔로부터 데이터를 입력 받는 기능을 제공

· Sytem.in은 InputStream 타입

· 키보드로부터 어떤 키가 입력되었는지 확인하려면 InputStream의 read() 메소드로 한 바이트를 읽기

System.out

· System 클래스의 out 정적 필드: 콘솔로 데이터를 출력하기 위한 기능 제공

Scanner 와 BufferedReader 차이는?

BufferedReader를 사용할때와 Scanner의 속도가 차이가 나는 이유는 buffer 사용 여부의 차이입니다.

Scanner는 1KB 크기의 버퍼를 갖기 때문에 입력이 바로 전달되는 반면 BufferedReader는 8KB 크기의 버퍼를 가져 buffer에 입력들을 저장하였다 한 번에 전송하기 때문에 속도가 더 빠릅니다.

또한, Scanner는 입력을 읽는 과정에서 내부에서 정규 표현식 적용, 입력값 분할, 파싱 과정 등을 거치기 때문에 속도가 느립니다.

1. Scanner 은 버퍼리더 보다 훨씬 활용적이다.

스캐너는 정수 값으로 int, short, long, 소수값으로 float,double를 구분지어 읽어들일 수 있고 String 값도 읽을 수있다. 반면에 BufferReader은 문자열 String값 밖에 읽지 못한다.

2. BufferedReader은 Scanner에 비해 상당히 큰 버퍼를 차지한다.

Scanner의 경우 (1KB) , BufferedReader은 (8KB) , 이것은 즉 긴 문자열이 포함된 파일을 읽을 시에는 BufferedReader을


질문

br 로 읽고 코드 내부에서 파싱을 처리한다면 scanner 와 과정은 유사한데 왜 br 이 더 빠를까 ?

답변

scanner 는 어떤타입인지 판단해야하므로 정규표현식을 반드시 써야한다 - 시간 걸림 정규 표현식도 모든 경우의 수가 들어가야하므로 그만큼 복잡함 하지만 br 은 프로그래머가 판단가능 - 시간 적게 걸림 따라서 br 이 더 짧게 걸린다 - 과일예시 https://lasbe.tistory.com/48 내용이 짧을경우 Scanner을 사용하는 것을 추천한다.


3. BufferedReader은 Scanner보다 더 오래되었다.

BufferedReader 은 JDK 1.1 에서 부터 존재해 왔고 Scanner은 JDK 1.5realse 에서 처음 소개 됬다.

4. Scanner은 정규식을 사용하여 값을 파싱한다.

예를 들어 int, long, short, florat 혹은 double의 경우 nextInt(),nextLong(),nextShort(),nextFloat(), nextDouble() 과같은

함수들을 사용한다. 반면에 BufferedReader은 오직 문자열 값만을 읽기 때문에 readLine() 함수만을 사용한다.


참고 - IO 개념


프로그래머스 수업내용 + 소스코드 예제


입출력을 위한 인터페이스와 클래스들

  • 자바 IO는 크게 byte단위 입출력과 문자 단위 입출력클래스로 나뉩니다.
    • byte단위 입출력클래스는 모두 InputStream과 OutputStream이라는 추상클래스를 상속받아 만들어집니다.
    • 문자(char)단위 입출력클래스는 모두 Reader와 Writer라는 추상클래스를 상속받아 만들어집니다.
  • 4가지 추상클래스(InputStream,OutputStreamReader,Reader,Writer)를 받아들이는 생성자가 있다면, 다양한 입출력방법을 제공하는 클래스입니다.
  • 4가지 클래스를 받아들이는 생성자가 없다면, 어디로부터 입력받을 것인지, 어디에 쓸것인지를 나타내는 클래스입니다.
  • 파일로 부터 입력받고 쓰기 위한 클래스 : FileInputStream, FileOutputStream, FileReader, FileWriter
  • 배열로 부터 입력받고 쓰기 위한 클래스 : ByteArrayInputStream, ByteArrayOutputStream, CharReader, CharWriter
    • 해당 클래스들은 어디로부터, 어디에라는 대상을 지정할 수 있는 IO클래스입니다. 이런 클래스를 장식대상 클래스라고 합니다.
  • DataInputStream, DataOutputStream같은 클래스를 보면 다양한 데이터 형을 입력받고 출력합니다.
  • PrintWriter는 다양하게 한줄 출력하는 pintln()메소드를 가지고있습니다.
  • BufferedReader는 한줄 입력받는 readLine()메소드를 가집니다.
    • 이런 클래스들은 다양한 방식으로 입력하고, 출력하는 기능을 제공합니다. 이런 클래스를 장식하는 클래스라고 합니다.

Byte 단위 입출력

Byte단위 입출력 클래스는 클래스의 이름이 InputStream이나 OutputStream으로 끝납니다.

파일로 부터 1byte씩 읽어들여 파일에 1byte씩 저장하는 프로그램을 작성 파일로 부터 읽어오기 위한 객체 - FileInputStream 파일에 쓸수 있게 해주는 객체 - FileOutputStream . read()메소드가 byte를 리턴한다면 끝을 나타내는 값을 표현할 수가 없기 때문에, byte가 아닌 int를 리턴한다. 음수의 경우 맨 좌측 비트가 1이 된다. 읽어들일 것이 있다면 항상 양수를 리턴한다고볼 수 있다. . FileInputStream과 FileOutputStream을 이용하여, 1바이트씩 읽어들여 1바이트씩 저장 read()메소드가 리턴하는 타입은 정수인데, 정수 4바이트중 마지막 바이트에 읽어들인 1바이트를 저장한다. read()메소드는 더이상 읽어들일 것이 없을때 -1을 리턴한다.

package programmers.intermediate.IO;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteExam1 {
    public static void main(String[] args){
        long startTime = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("src/programmers/intermediate/IO/1.txt");
            fos = new FileOutputStream("byte.txt");

            int readData = -1;
            while((readData = fis.read())!= -1){
                fos.write(readData);
                System.out.println(readData);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                fos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }


        long endTime = System.currentTimeMillis();

        System.out.println(endTime - startTime);
    }
}

Byte 단위 입출력 심화

Byte단위 입출력 클래스는 클래스의 이름이 InputStream이나 OutputStream으로 끝납니다.

파일로 부터 512byte씩 읽어들여 파일에 512byte씩 저장하는 프로그램을 작성 byte[] buffer = new byte[512]; 512byte만큼 읽어 들이기 위해 byte배열을 사용

package programmers.intermediate.IO;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteExam2 {
    public static void main(String[] args){
        long startTime = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("src/programmers/intermediate/IO/ByteExam2.java");
            fos = new FileOutputStream("byte.txt");

            int readCount = -1;
            byte[] buffer = new byte[512];
            while((readCount = fis.read(buffer))!= -1){
                // read 메서드 - 읽을 거리가 없을 경우 -1일 반환
                fos.write(buffer, 0, readCount);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                fos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        long endTime = System.currentTimeMillis();

        System.out.println(endTime - startTime);
    }
}

다양한 타입의 출력

try-with-resources 블럭 선언 java io객체는 인스턴스를 만들고, 모두 사용하면 close()메소드를 호출해야 한다. close()메소드를 사용자가 호출하지 않더라도, Exception이 발생하지 않았다면 자동으로 close()가 되게 할 수 있는 방법

package programmers.intermediate.IO;

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class ByteExam3 {
    public static void main(String[] args) {
        try (
                DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));
        ) {
            out.writeInt(100); // 정수를 저장할 경우 4bytes
            out.writeBoolean(true); // boolean을 저장할 경우 1byte
            out.writeDouble(1.123); // double을 저장할 경우 8bytes

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

다양한 타입의 입력

data.dat로부터 값을 읽어들여 화면에 출력하는 클래스

다양한 타입의 데이터를 읽어낼 수 있는 DataInputStream

readInt() -정수를 읽어들이는 메소드 readBoolean() - boolean 값을 읽어들이는 메소드 readDouble() - douboe 값을 읽어들이는 메소드

package programmers.intermediate.IO;

import java.io.DataInputStream;
import java.io.FileInputStream;

public class ByteExam4 {

    public static void main(String[] args) {
        try (
                DataInputStream in = new DataInputStream(new FileInputStream("data.txt"))
                /**
                 * data를 읽어들여야 하기 때문에 inputStream 객체를 생성하고 어떤 파일을 받아들일지를 정해준다.
                 */
                ) {
            int i = in.readInt(); // FileInputStream의 read 메서드를 통해서 file을 읽어들임
            boolean b = in.readBoolean();
            double d = in.readDouble();
            /**
             * 그럼 항상 위와같이 순서를 맞춰 읽어야만 하나 ??
             */

            System.out.println(i);
            System.out.println(b);
            System.out.println(d);
            /**
             * 데이터타입 자체로 저장하고 해당 데이터 타입으로 불러내어 출력을 하면 눈으로 볼 수 있도록 출력됨
             */
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Char 단위 입출력(Console)

char단위 입출력 클래스는 클래스 이름이 Reader나 Writer로 끝이 납니다.

char단위 입출력 클래스를 이용해서 키보드로 부터 한줄 입력 받아서 콘솔에 출력 System.in - 키보드를 의미 (InputStream ) BufferedReader - 한줄씩 입력 받기위한 클래스 BufferedReader 클래스의 생성자는 InputStream을 입력받는 생성자가 없다. System.in은 InputStream 타입이므로 BufferedReader의 생성자에 바로 들어갈 수 없으므로 InputStreamReader 클래스를 이용해야함.

package programmers.intermediate.IO;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class CharIOExam1 {
    public static void main(String[] args) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        /**
         * System.in 을 바로 쓸 수가 없음 왜냐하면 reader 타입만 사용할 수 있으므로
         * inputStream인 system.in을 reader 타입으로 바꿔야 함
         * 키보드로부터 한줄씩 입력 받을 수 있음
         */

//        String line = br.readLine(); readline은 thorws IO예외를 하고 있음
        String line = null;
        try {
            line = br.readLine();
        } catch (IOException e) {
//            throw new RuntimeException(e); // 자동생성
            e.printStackTrace(); // 왜 똑같이 안되지 ?
        }
        System.out.println(line);
    }
}

Char 단위 입출력(File)

char단위 입출력 클래스는 클래스 이름이 Reader나 Writer로 끝이 납니다.

파일에서 한 줄씩 입력 받아서 파일에 출력 파일에서 읽기위해서 FileReader 클래스 이용 한 줄 읽어 들이기 위해서 BufferedReader 클래스 이용 BufferedReader 클래스가 가지고 있는 readLine() 메소드가 한줄씩 읽게 해준다. readLine()메소드는 읽어낼 때 더 이상 읽어 들일 내용이 없을 때 null을 리턴한다. 파일에 쓰게하기 위해서 FileWriter 클래스 이용 편리하게 출력하기 위해서 PrintWriter 클래스 이용

package programmers.intermediate.IO;

import java.io.*;

public class CharIOExam2 {
    public static void main(String[] args) {
        BufferedReader br = null;
        PrintWriter pw = null;
        try {
            br = new BufferedReader(new FileReader("src/programmers/intermediate/IO/CharIOExam2.java"));
            pw = new PrintWriter(new FileWriter("test.txt"));

            String line = null;
            /**
             * // 파일로부터 한줄 입력받은것이 라인에 들어감 리턴된 값이 null일때까지 반복함
             * 파일이 끝이 되면 null을 반환함 아까 -1 반환과 비슷한 과정
             * IO는 항상 열어주면 닫아주자 close를 하지 않으면 아무 값이 저장되지 않음
             */
            while ((line = br.readLine()) != null) {
                pw.println(line);
            }
            /**
             * 파일이 없을 수 있는 예외(에러아님) 가 발생할 수 있기 때문에 예외 처리를 해준다.
             * 예외처리는 매우 중요함 ..!
             * 강의에서는 try catch 문으로 예외처리를 하였는데 import java.io.FileNotFoundException 로도 할 수 있다 둘의 차이는 뭘까 ?
             */
        } catch (Exception e) {
//            throw new RuntimeException(e);
            e.printStackTrace(); // 아까와 같은 에러
        } finally {
            pw.close();
            try {
                br.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}


참고 - 프로그래머스 중급