[프로그래머스] 자바 중급 - IO / stream, reader, writer

정현우·2021년 4월 2일
3

Java Basic

목록 보기
5/8

자바 IO (intput output)

Java에서 입출력(IO)을 위한 인터페이스와 클래스들

Basic of IO

input과 output

  • 간단하게 프로그램에 들어오는 모든 입력과 모든 출력
  • 자바 IO는 크게 byte단위 입출력과 문자 단위 입출력클래스로 나뉩니다.

  • byte단위 입출력클래스는 모두 InputStream과 OutputStream이라는 추상클래스를 상속받아 만들어집니다.
  • 문자(char)단위 입출력클래스는 모두 Reader와 Writer라는 추상클래스를 상속받아 만들어집니다.
  • 그렇기 때문에 특별한 방법(다른방법)으로 입-출력을 하려면 4가지 추상 매서드를 생성자에서 받아들이는 IO class들을 이용해야한다!
  • 4가지 추상클래스(InputStream,OutputStreamReader,Reader,Writer)를 받아들이는 생성자가 있다면, 다양한 입출력방법을 제공하는 클래스입니다.
  • 4가지 클래스를 받아들이는 생성자가 없다면, 어디로부터 입력받을 것인지, 어디에 쓸것인지를 나타내는 클래스입니다.

  • 파일로 부터 입력받고 쓰기 위한 클래스 : FileInputStream, FileOutputStream, FileReader, FileWriter
  • 배열로 부터 입력받고 쓰기 위한 클래스 : ByteArrayInputStream, ByteArrayOutputStream, CharReader, CharWriter
  • 해당 클래스들은 어디로부터, 어디에라는 대상을 지정할 수 있는 IO클래스입니다. 이런 클래스를 장식대상 클래스라고 합니다.
    물이 나오는(흐르는) 호스에 '어떤 수도 꼭지를 달까? (장식할까?)'를 생각해보자!
  • DataInputStream, DataOutputStream같은 클래스를 보면 다양한 데이터 형을 입력받고 출력합니다.
  • PrintWriter는 다양하게 한줄 출력하는 pintln()메소드를 가지고있습니다.
  • BufferedReader는 한줄 입력받는 readLine()메소드를 가집니다.
  • 이런 클래스들은 다양한 방식으로 입력하고, 출력하는 기능을 제공합니다. 이런 클래스를 장식하는 클래스라고 합니다.


Byte 단위 입출력

Stream

  • 여담으로 먼저 스트림이 무엇인지 알아보자! -> 아 물론! 자바8에 등장한 스트림 패키지(람다 활용을 위한)를 말하는 것이 아니다! 그 스트림은 고급에서 다뤄보자!
  • 스트림?
    - 자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룬다.
    - 스트림(stream)이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미한다.
    - 즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할
    • 출처: tcpschool.com/java/java_io_stream
  • 자바에서 데이터는 스트림(Stream)을 통해 입출력 된다.
  • 스트림은 단일 방향으로 연속적으로 흘러가는 것을 말하는데 물이 높은 곳에서 낮은곳으로 흐르듯이 데이터는 출발지에서 나와 도착지로 흘러간다는 개념
  • 프로그램이 출발지냐 또는 도착지냐에 따라서 스트림의 종류가 결정되는데, 프로그램이 데이터를 입력받을 때에는 입력(InputStream)이라고 한다. 입력스트림의 출발지는 키보드, 파일, 네트워크상의 프로그램이 될 수 있고, 출력 스트림의 도착지는 모니터, 파일, 네트워크상의 프로그램이 될수 있다.
  • 항상 프로그램을 기준으로 데이터가 들어오면 입력스트림이고 데이터가 나가면 출력스트림이라고 생각!
  • 프로그램이 네트워크상의 다른 프로그램과 데이터를 교환을 하기 위해서는 양쪽 모두 입력 스트림과 출력스트림이 따로 필요!
  • 스트림은 단방향 통신을 한다는 특징이 있으므로 하나의 스트림으로 입출력을 동시에 할 수 없기 때문입니다.

InputStream / OutputStream

  • Byte단위 입출력 클래스는 클래스의 이름이 InputStream이나 OutputStream으로 끝납니다.
  • 파일로 부터 1byte씩 읽어들여 파일에 1byte씩 저장하는 프로그램을 작성
    - 파일로 부터 읽어오기 위한 객체 - FileInputStream
    - 파일에 쓸수 있게 해주는 객체 - FileOutputStream
    public class ByteIOExam1 {
        public static void main(String[] args){     
            FileInputStream fis = null; // 읽기 
            FileOutputStream fos = null; // 쓰기
            try {
                fis = new FileInputStream("src/javaIO/exam/ByteExam1.java");
                fos = new FileOutputStream("byte.txt");
				
                // 읽어들일때 while read가 핵심! 한 바이트 읽고 가져오기
                int readData = -1; 
                while((readData = fis.read())!= -1){
                    fos.write(readData);
                }           
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }finally{
            	// close할때도 error catch 해줘야한다 ㅎ
                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();
                }
            }
        }
    }
  • Try catch는 File이 없을때 캐치하기 위해서!
  • Finally는 try, catch 모두 들어온다! try catch의 기본
  • read()메소드가 byte를 리턴한다면 끝을 나타내는 값을 표현할 수가 없기 때문에, byte가 아닌 int를 리턴한다.
    - 한 바이트 씩 읽고, 정수의 4byte 중 마지막 바이트에 읽은 한 바이트를 저장한다.
    • 음수의 경우 맨 좌측 비트가 1이 된다. 읽어들일 것이 있다면 항상 양수를 리턴한다고볼 수 있다. .
    • read()메소드는 더이상 읽어들일 것이 없을때 -1을 리턴한다.
  • FileInputStream과 FileOutputStream을 이용하여, 1바이트씩 읽어들여 1바이트씩 저장
    위 코드를 러닝하면 byte.txt 파일로 결과를 확인 가능하다!

1byte 씩 -> 512byte 씩으로

  • buffer를 사용하자! -> 간단하게 버퍼만큼 한 꺼번에 읽겠다!
    - byte[] buffer = new byte[512];
    • 512byte만큼 읽어 들이기 위해 byte배열을 사용
    public class ByteIOExam1 {
        public static void main(String[] args){     
            //메소드가 시작된 시간을 구하기 위함
            long startTime = System.currentTimeMillis();        
            FileInputStream fis = null; 
            FileOutputStream fos = null;        
            try {
                fis = new FileInputStream("src/javaIO/exam/ByteExam1.java");
                fos = new FileOutputStream("byte.txt");

                int readCount = -1; 
                byte[] buffer = new byte[512];
                while((readCount = fis.read(buffer))!= -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); 
        }
    }
  • 왜 이렇게 속도 차이가 나는 걸까?
  • 우리가 사용하는 OS는 1byte씩 읽어오라고 해도 512byte씩 읽어온다.
    - OS disk block의 기본 크기로 인한 IO 때문
  • 그래서 1byte를 읽어오라고 하면 511byte 버리고 1byte만 읽어 버린다.
  • 그래서 512byte씩 크기를 잡아주는게 성능상 훨씬 좋다!

다양한 타입의 입-출력

read를 통해 기본적으로 String, 즉 문자로 읽는 것은 알겠다,, 근데 String이 아니라 다양한 Data Type으로 읽고 써야 한다면??

try-with-resources 블럭

  • java io객체는 인스턴스를 만들고, 모두 사용하면 close()메소드를 호출해야 한다.
  • close()메소드를 사용자가 호출하지 않더라도, Exception이 발생하지 않았다면 자동으로 close()가 되게 할 수 있는 방법
	try(
	// 괄호 부분에 io객체 선언
    	// DataOutputStream은 다양한 타입으로 object 저장 가능! 
	) {
	//io객체 사용
	}catch(Exception ex){
		ex.printStackTrace();
	}
  • 다양한 타입으로 저장 할 수 있는 DataOutputStream
  • 생성자는 OutputStream을 매개변수로 받아들인다 -> OutputStream 자손이라면 무엇이든지 받아들인다!
  • DataOutputStream은 '장식의 역할' -> 다양한 메서드만 제공한다! 하지만, 어디에 써라! 라는 부분은 사용할 수 없다. 그래서 어디에 쓸껀지 메서드를 같이 사용하면 된다!
    - wirteInt() - 정수값으로 저장
    - wirteBoolean() - boolean값으로 저장
    - writeDouble() - double 값으로 저장
    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"));
            ){
		// 실제 사용할 코드! -> write를 다양하게 오버라이딩하고 있다!
                out.writeInt(100); // 4byte 저장
                out.writeBoolean(true); // 1byte 저장
                out.writeDouble(50.5); // 8byte 저장
            }catch (Exception e) {
                e.printStackTrace();
            }
        }   
    }
  • 파일을 열어보면 우리가 식별할 수 없는 형태로 보여진다! -> 쓸 때 데이터 타입을 이용해서 썻기 때문이다! -> 읽을때도 해당하는 데이터 타입으로 읽어내야 한다.
  • 파일 크기를 확인해보자! (13바이트)
  • 다양한 타입의 데이터를 읽어낼 수 있는 DataInputStream!!
    - readInt() -정수를 읽어들이는 메소드
    - readBoolean() - boolean 값을 읽어들이는 메소드
    - readDouble() - douboe 값을 읽어들이는 메소드
    import java.io.DataInputStream;
    import java.io.FileInputStream;

    public class ByteIOExam4 {

        public static void main(String[] args) {
            try(
            	DataInputStream out = new DataInputStream(new FileInputStream("data.dat"));
            ){
                int i = out.readInt();          
                boolean b = out.readBoolean();          
                double d = out.readDouble();

                System.out.println(i);
                System.out.println(b);
                System.out.println(d);
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
  • 유의할 점은 '저장된 순서대로' 읽어야 한다는 것 -> 하지만 순서대로 type이 다른걸 생각하자!
    - int, boolean, double순서대로 저장하였기 때문에 읽어들일 때도 같은 순서로 읽어여야 한다.
    • 위 class 코드 파일로 콘솔을 확인해보자!

Char 단위 입출력

Reader, Writer

  • char단위 입출력 클래스는 클래스 이름이 Reader나 Writer로 끝이 난다! API DOCS
  • 살짝 여담으로 IO을 직접 만들어야하는 알고리즘, 특히 백준에서 유용하게 쓸 수 있다!
  • 근원지 ~ 기능 -> 끼워서 사용! '데코레이터 패턴'을 떠올려 보자!
  • char단위 입출력 클래스를 이용해서 키보드로 부터 한줄 입력 받아서 콘솔에 출력 코드 만들어보자!
    - System.in : 키보드를 의미 (InputStream)
    - BufferedReader : "한줄씩 입력" 받기위한 클래스
    - BufferedReader 클래스의 생성자는 InputStream을 입력받는 생성자가 없다.
    - System.in은 InputStream 타입이므로 BufferedReader의 생성자에 바로 들어갈 수 없으므로 InputStreamReader 클래스를 이용해야함.
    import java.io.BufferedReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter; 
    public class CharIOExam01 {
        public static void main(String[] args) {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            //키보드로 입력받은 문자열을 저장하기 위해 line변수를 선언               
            String line = null;     
            try {
                line = br.readLine()
            } catch (IOException e) {
                e.printStackTrace();
            }
            //콘솔에 출력 
            System.out.println(line);
        }
    }

File 단위

  • 파일에서 한 줄씩 입력 받아서 파일에 출력
  • 파일에서 읽기위해서 FileReader 클래스 이용
    - 한 줄 읽어 들이기 위해서 BufferedReader 클래스 이용
    • 생성자에서 FileReader Object를 통해 만들자!
      - BufferedReader 클래스가 가지고 있는 readLine() 메소드가 한줄씩 읽게 해준다.
    • PS) 한줄씩? -> CR LF, 캐리지 리턴과 라인피드
      - readLine()메소드는 읽어낼 때 더 이상 읽어 들일 내용이 없을 때 null을 리턴한다.
  • 파일에 쓰게하기 위해서 FileWriter 클래스 이용
  • 편리하게 출력하기 위해서 PrintWriter 클래스 이용
  • PrintWriter가 업데이트 되면서 생성자 자체가 File을 받아들이는 부분도 제공한다! -> FileWriter를 꼭 쓸필요가 없어졌다!
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter; 
    public class CharIOExam02 {
        public static void main(String[] args) {
	    // 제대로 error catch를 하기 위해 선언부와 입력부를 따로!
            BufferedReader br = null; // 입력을 위해
            PrintWriter pw = null; // 출력을 위해 
            try{        
                br = new BufferedReader(new FileReader("src/javaIO/exam/CharIOExam02.java"));
                pw = new PrintWriter(new FileWriter("test.txt"));
                String line = null;
                // 여러줄 읽기위해 반복문
                while((line = br.readLine())!= null){
                    pw.println(line);
                }
            }catch(Exception e){
            	// file not found error catch! 
                e.printStackTrace();
            }finally {
                pw.close();
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • IO는 열었으면 항상 닫아줘야한다! 닫아 주지않으면 입력 런 한 코드가 무용지물이 될 수 있다!
  • '어디에서' 읽을꺼냐 '어디에서' 쓸꺼냐, '어떤 방법'으로 읽을꺼냐, '어떤 방법'으로 써줄거냐 -> 이에 따라 아주 다양한 조합과 방법으로 IO가 가능해진다!
profile
도메인 중심의 개발, 깊이의 가치를 이해하고 “문제 해결” 에 몰두하는 개발자가 되고싶습니다. 그러기 위해 항상 새로운 것에 도전하고 노력하는 개발자가 되고 싶습니다!

0개의 댓글