char 단위의 입/출력 클래스는 이름이 Reader 혹은 Writer로 끝난다.
이런 char 단위 입출력 클래스를 이용해서 키보드로부터 문자열 한 줄을 입력 받는 프로그램을 구현해보자.

Char 단위 입출력(Console)

System.in

키보드로부터 문자열을 입력받아 콘솔에 출력하기 위해서는 System.in 을 사용하면 된다.

그리고 System.in 은 위 오라클 도큐먼트 이미지에서 알 수 있듯
java.lang 패키지, System 클래스에서 InputStream type의 in 필드를 가진다.

BufferedReader

그리고 한 줄을 입력받기 위해서는 BufferedReader 메소드를 사용해야 한다.
그런데 문서를 보니 BufferedReader() 의 매개변수는 Reader 타입이다.

아니, <콘솔>에서 (한 줄) 을 입력받고 싶은데
콘솔에서 입력을 받으려면 필요한 <System.in>InputStream 타입이고,
한 줄을 입력받기 위해 필요한 (BufferedRader)은 매개변수가 Reader 타입이라니!

그렇다면 System.in을 Reader 타입으로 바꿔주어야 할텐데,
이 때 사용하는 것이 InputStreamReader 클래스이다.

InputStreamReader

InputStreamReader 클래스는 Reader를 상속받는 Reader 타입이다.
그리고 생성자의 매개변수는 InputStream 타입을 받고 있다.

그래! InputStreamReader 클래스를 사용하면 System.in과
BufferedReader를 사용해 키보드로부터 문자열 한 줄을 입력 받을 수 있을 것이다.

	import java.io.BufferedReader;
    import java.io.InputStreamReader;
    
    public class CharIOExam01 {
        public static void main(String[] args) {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        }
    }

먼저, 한 줄을 입력받기 위해 BufferedReader() 를 생성한다.
키보드로 입력받을 것이므로 () 안에는 System.in 을 넣어야 하는데
타입이 맞지 않아 바로 넣을 수는 없으니 System.in을
Reader 타입으로 바꿔주는 InputStreamReader 사용한다.

Decorator Pattern

데코레이터 패턴은 하나의 클래스를 마치 장식하는 것처럼 생성자에 감싸서
새로운 기능을 계속 추가할 수 있도록 클래스를 만드는 방식이다.

이와 같이 객체에 추가적인 요건을 맞추어 특정 기능을 동적으로 사용할 수 있도록 하는 방식을 데코레이터 패턴이라고 한다.

구현 코드

 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);
        }
    }

BufferedReader 객체가 가진 readLine() 메소드를 사용해서
키보드로부터 입력받은 값을 String line 변수에 넣어준다.
그리고 이 line 변수를 밖에서도 선언하기 위해서 try 블록 밖에 위치하게 한다.

그런데 line = br.readLin() 줄에서 Exception이 발생할 수 있으므로
try catch문으로 예외처리를 해줘야 한다.

코드를 실행하면 br.readLine()이 실행될 때 키보드로부터 한 줄을 입력받아
line 변수에 넣어진 다음 console에 line 값이 출력된다.

이런 char 단위 입출력 클래스를 반복문으로 구현한다면 여러 줄도 입력 받을 수 있고
데코레이터 패턴으로 구현한다면 키보드 말고 파일로도 입력 받을 수 있다.
외에도 다양한 방법을 생각해볼 수 있다.

Char 단위 입출력(File)

이번에는 파일로 입력받고 파일로 출력하는 프로그램을 구현해보자.

FileReader & BufferedReader

파일로 받기 위해서는 파일로 읽어주는 FileReader 객체가 필요하고
문자열을 한줄씩 읽기 위해서는 BufferedReader 객체가 필요하다.
그래서 두 개의 객체가 서로 "같이" 생성 되어야 한다.

 	import java.io.BufferedReader;
    import java.io.FileReader;
    
    public class CharIOExam02 {
        public static void main(String[] args) {
        
                BufferedReader br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
        }
    }

우선 BufferedReader 가 생성될 때,
파일에서 입력받기 위한 FileReader 객체를 생성한다.

이때 () 안에는 읽고싶은 파일의 경로를 쓰면 된다.
이클립스 기준으로는 프로젝트가 현재 기준이 되므로 src 경로부터 작성해주면 된다.

public static void main(String[] args) {
            BufferedReader br = null; 
            try{        
                br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
                } catch(Exception e){
                ...
                } 
                ...
}

new FileReader가 생성될 때 FileNotFoundException이 발생하면
try catch문으로 예외처리를 해주어야하고,

이때 BufferedReader를 try블록 밖에서도 사용하고 싶다면
BufferedReader의 선언을 아래처럼 try 블록 밖에서 해야한다.

여기까지 파일의 한 줄을 읽어들일 준비가 완료되었다.

FileWriter & PrintWriter

이제 파일을 쓰는 코드를 작성할 차례이다.

파일에 쓸 수 있는 객체는 FileWriter를 쓰면 되는데,
FileWriter는 write() 메소드가 굉장히 단순하게 구현되어있다.
대표적으로 System.out.println의 System.out 타입이 PrintWriter 객체이다.

PrintWriter는 다양한 방법으로 출력하는 메소드들을 가지고 있다.
그래서 출력을 할 때에는 PrintWriter 객체를 이용하는 것이 편리하다.

 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) {
            BufferedReader br = null; 
            PrintWriter pw = null;
            try{        
                br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
                pw = new PrintWriter(new FileWriter("test.txt"));
              
            } catch(Exception e){
                e.printStackTrace();
            }
        }
    }

파일에서 받을 것이므로, 파일에서 받게 해주는 객체인 FileWriter를 사용한다.
new FileWriter()의 () 안에는 저장할 파일의 경로와 함께 이름을 적으면 된다.
경로를 적지 않으면 해당 프로젝트 밑에 위치하게 된다.

위 예시는 데코레이터 패턴을 사용하고 있지만
PrintWriter 생성자 자체가 파일을 받아들이는 기능도 제공하고 있어서
FileWriter를 쓰지 않고, PrintWriter만 사용하는 것도 가능하다.

try{
	br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
    pw = new PrintWriter(new FileWriter("test.txt"));
    String line = null;
    while((line = br.readLine())!= null){
    	pw.println(line);
    	}
}catch(Exception e){
	e.printStackTrace();
}

파일이 한 줄이 아니고, 여러 줄이 있을 수도 있으니 반복문을 사용한다.
파일에 더 읽을 것이 없다면 null이 리턴되므로, 리턴 값이 null일 때까지 수행한다.
한 줄을 읽었을 때 저장할 수 있는 공간은 String line 변수를 선언해 사용한다.
String line 변수 안에 읽어들인 값을 다시 파일에 쓸 때는 PrintWriter pw 객체를 쓴다.

구현 코드

 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) {
            BufferedReader br = null; 
            PrintWriter pw = null;
            try{        
                br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
                pw = new PrintWriter(new FileWriter("test.txt"));
                String line = null;
                while((line = br.readLine())!= null){
                    pw.println(line);
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                pw.close();
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

IO는 반드시 닫아주어야 하므로, Exception 발생 여부와 상관 없이
무조건 실행하는 finally 블럭에 close() 메소드를 호출하여 마무리한다.

코드 실행 후 프로젝트 위치에서 새로고침을 하면
test.txt 파일이 생성되어있는 것을 확인할 수 있다.

자바 io 패키지는 "어디에서 읽을 것인지", "어디에다가 쓸 것인지", "어떤 방식으로 읽을 것인지", "어떤 방식으로 쓸 것인지"의 경우에 따라서 여러가지 객체의 종류들을 연결하고 조합해 사용할 수 있다.

🙏 Reference

0개의 댓글