본문 바로가기
☕ Java 웹 프로그래밍/Java

[프로그래머스] Java(자바) 중급 | Part 5. IO

by 일단연 2023. 5. 15.

 자바IO 

자바 IO

  • 입출력을 위한 인터페이스와 클래스들
  • IO에서 I는 Input, O는 Output
  • 프로그램 상으로 들어오는 모든 데이터를 Input Data, 프로그램에서 파일, 네트워크, 콘솔 등으로 나가는 모든 데이터를 Output Data라고 함
  • 자바 IO는 데코레이터 패턴(Decorator Pattern)으로 만들어짐
    • 데코레이터 패턴: 장식하는 것처럼, 하나의 클래스를 생성자에서 감싸서 새로운 기능을 계속 추가할 수 있도록 클래스를 만드는 방식
  • Java에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 모든 IO가 스트림(Stream)을 통해 이루어짐

 

스트림(Stream)

  • 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름
  • 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 함
  • 한 방향으로만 통신할 수 있으므로, 입력과 출력을 동시에 처리할 수는 없음
  • 선입선출 구조(First In First Out): 먼저 들어온 게 먼저 나감
  • 시간에 따라 순차적으로 받음 (타임 슬라이싱)
  • 사용 목적에 따라 입력 스트림과 출력 스트림으로 구분됨
    • Java에서는 java.io 패키지를 통해 InputStream과 OutputStream 클래스를 별도로 제공
      • InputStream: 입력장치로부터 Java 프로그램으로 데이터를 전달
      • OutputStream: 출력장치로부터 데이터를 출력
  • Java에서의 스트림 생성이란 스트림 클래스 타입의 인스턴스를 생성한다는 의미

 

장식대상 클래스와 장식하는 클래스

  • 자바 IO가 데코레이터 패턴(Decorator Pattern)으로 만들어졌기 때문에 장식하는 클래스, 장식대상 클래스라는 표현이 쓰임
  • 장식하는 클래스와 장식대상 클래스는 4가지 추상클래스(InputStream, OutputStream, Reader, Writer)를 매개변수로 받아들이는 생성자의 존재 여부로 나뉨

 

장식하는 클래스

  • 개념
    • 4가지 추상 클래스를 매개변수로 받아들이는 생성자가 있는 경우
    • 어디에 쓸지는 결정할 수 없기 때문에 이를 결정하는 다른 장식대상 클래스를 함께 써야 함
    • 다양한 입출력 방법을 제공하는 자바 IO 클래스
      • 입출력 데이터를 처리할 때 장식대상 클래스보다 더 편한 방식을 제공
  • 종류
    • 다양한 데이터타입을 입력받고 출력
      • DataOutputStream: 다양한 타입으로 데이터를 저장할 수 있는 클래스
      • DataInputStream: 다양한 타입의 데이터를 읽어낼 수 있는 클래스
      • BufferedReader: 한 줄씩 입력받기 위한 클래스
        • 한 줄 입력받는 readLine( )메소드를 가짐
      • PrintWriter: 편리하게 출력할 수 있게 하는 클래스
        • 다양하게 한 줄 출력하는 println( )메소드를 가짐

 

장식대상 클래스

  • 개념
    • 4가지 추상 클래스를 매개변수로 받아들이는 생성자가 없는 경우
    • 어디로부터, 어디에라는 대상을 지정할 수 있는 IO클래스
    • 대상이 되는 부분으로부터 무조건 값을 입력받거나 출력하는 클래스
    • 1byte나 1char 단위로 입출력 > 입출력 메소드가 단순하게 제공된다는 뜻
  • 종류
    • 파일로부터 입력받고 쓰기 위한 클래스
      • FileInputStream: 파일로부터 읽어오기 위한 클래스
      • FileOutputStream: 파일에 쓸 수 있게 해주는 클래스
      • FileReader: 파일에서 읽어오는 클래스
      • FileWriter: 파일에 쓰는 클래스
    • 배열로부터 입력받고 쓰기 위한 클래스
      • ByteArrayInputStream: 바이트 배열로부터 읽어오기 위한 클래스
      • ByteArrayOutputStream: 바이트 배열에 데이터를 쓸 수 있게 해주는 클래스
      • CharReader: CharArrayReader를 상속받음
      • CharWriter: CharArrayWriter를 상속받음

 

장식하는 클래스와 장식대상 클래스의 상호작용

  • 파일에 데이터 쓰기: DataOutputStream과 FileOutputStream:
DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));
  • 파일로부터 데이터를 읽어오기: DataInputStream과 FileInputStream
DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));
  • 키보드로부터 한 줄 입력받아서 콘솔에 출력하기: BufferedReader와 InputStreamReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  • 파일에서 한 줄씩 입력받아서 파일에 출력하기: BufferedReader와 FileReader, PrintWriter와 FileWriter
BufferedReader br = new BufferedReader(new FileReader("src/javaIO/exam/CharIOExam02.java"));
PrintWriter pw = new PrintWriter(new FileWriter("test.txt"));

 

입출력 스트림

  • InputStream 클래스에는 read() 메소드가, OutputStream 클래스에는 write() 메소드가 각각 추상 메소드로 포함되어 있음
  • 사용자는 이 두 메소드를 상황에 맞게 적절히 구현해야만 입출력 스트림을 생성하여 사용할 수 있음
클래스 메소드 설명
InputStream abstract int read() 해당 입력 스트림으로부터 다음 바이트를 읽어들임
int read(byte[] b) 해당 입력 스트림으로부터 특정 바이트를 읽어들인 후, 배열 b에 저장
int read(byte[] b, int off, int len) 해당 입력 스트림으로부터 len 바이트를 읽어들인 후, 배열 b[off]부터 저장
OutputStream abstract void write(int b) 해당 출력 스트림에 특정 바이트를 저장
void write(byte[] b) 배열 b의 특정 바이트를 배열 b의 길이만큼 해당 출력 스트림에 저장
void write(byte[] b, int off, int len) 배열 b[off]부터 len 바이트를 해당 출력 스트림에 저장
  • read( ) 메소드의 리턴 타입만 int형인 이유
    • read( ) 메소드는 해당 입력 스트림에서 더 이상 읽어들일 바이트가 없으면, -1을 반환해야 함
    • But, 반환 타입을 byte 타입으로 하면 0부터 255까지의 바이트 정보는 표현할 수 있지만 -1은 표현할 수 없음
    • 따라서 InputStream의 read( ) 메소드는 리턴타입을 int형으로 선언

 

byte 기반 스트림과 문자 기반 스트림

  • 자바 IO는 크게 byte 단위의 입출력 클래스과 문자(char) 단위의 입출력 클래스로 나뉨
    • byte 단위 입출력클래스: InputStream과 OutputStream이라는 추상클래스를 상속받아 만들어짐
    • 문자(char) 단위 입출력클래스: Reader와 Writer라는 추상클래스를 상속받아 만들어짐

 

byte 기반 스트림

  • Java에서 스트림은 기본적으로 바이트 단위로 데이터를 전송
입력 스트림 출력 스트림 입출력 대상
FileInputStream FileOutputStream 파일
ByteArrayInputStream ByteArrayOutputStream 메모리
PipedInputStream PipedOutputStream 프로세스
AudioInputStream AudioOutputStream 오디오 장치

 

byte 기반 보조 스트림

  • Java에서 제공하는 보조 스트림은 실제로 데이터를 주고받을 수는 없지만, 다른 스트림의 기능을 향상시키거나 새로운 기능을 추가해 주는 스트림
입력 스트림 출력 스트림 설명
FilterInputStream FilterOutputStream 필터를 이용한 입출력
BufferedInputStream BufferedOutputStream 버퍼를 이용한 입출력
DataInputStream DataOutputStream 입출력 스트림으로부터 자바의 기본 타입으로 데이터를 읽어올 수 있게 함
ObjectInputStream ObjectOutputStream 데이터를 객체 단위로 읽거나, 읽어 들인 객체를 역직렬화시킴
SequenceInputStream X 두 개의 입력 스트림을 논리적으로 연결
PushbackInputStream X 다른 입력 스트림에 버퍼를 이용해 push back이나 unread와 같은 기능을 추가
X PrintStream 다른 출력 스트림에 버퍼를 이용해 다양한 데이터를 출력하기 위한 기능을 추가

 

문자 기반 스트림

  • 자바에서 스트림은 기본적으로 byte 단위로 데이터를 전송하지만, 가장 작은 타입인 char 형이 2바이트이므로, 1바이트씩 전송되는 byte 기반 스트림으로는 원활한 처리가 힘든 경우가 있음
  • 따라서 byte 기반 스트림뿐만 아니라 문자 기반의 스트림도 별도로 제공
  • 이러한 문자 기반 스트림은 기존의 byte 기반 스트림에서 InputStream을 Reader로, OutputStream을 Writer로 변경하면 사용할 수 있음
입력 스트림 출력 스트림 입출력 대상
FileReader FileWriter 파일
CharArrayReader CharArrayWriter 메모리
PipedReader PipedWriter 프로세스
StringReader StringWriter 문자열

 

문자 기반 보조 스트림

입력 스트림 출력 스트림 설명
FilterReader FilterWriter 필터를 이용한 입출력
BufferedReader BufferedWriter 버퍼를 이용한 입출력
PushbackReader X 다른 입력 스트림에 버퍼를 이용하여 push back이나 unread와 같은 기능을 추가
X PrintWriter 다른 출력 스트림에 버퍼를 이용하여 다양한 데이터를 출력하기 위한 기능을 추가

 


 

 Byte 단위 입출력 

Byte단위 입출력 클래스는 클래스의 이름이 InputStream이나 OutputStream으로 끝남

 

파일로부터 1byte씩 읽어들여, 파일에 1byte씩 저장하는 프로그램

  • FileInputStream: 파일로부터 1byte씩 읽어들임
  • FileOutputStream: 파일에 1byte씩 저장

 

완성 코드

  • 파일로부터 1byte씩 읽어들여 파일에 1byte씩 저장하는 코드
public class ByteIOExam2 {
  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");
      
      //읽어들인 값을 담기 위해 readData라는 변수 선언
      int readData = -1;
      //읽어들일 코드가 여러 줄이기 때문에 반복문 while 이용
      //읽어들이는 데이터를 readData에 담아줄 거니까 readData = fis.read()
      //파일의 데이터를 다 읽을 때까지 수행시켜야 하니까 -1이 아닐 때만 반복(-1이면 반복문 종료)
      while((readData = fis.read()) != -1){
        fos.write(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();
        e.printStackTrace();
      }
      try {
        fis.close();
      } catch (IOException e) {
        *// TODO Auto-generated catch block*
        e.printStackTrace();
      }
    }
  }
}

 

파일로부터 1byte씩 읽어들여 파일에 1byte씩 저장하는 코드를 작성하는 과정

객체 선언 및 생성

  • FileInputStream: 파일로부터 읽어오기 위한 java.io의 클래스
  • FileOutputStream: 파일에 쓸 수 있게 해주는 java.io의 클래스
//객체를 선언함과 동시에 초기화
FileInputStream fis = null;
FileOutputStream fos = null;

//객체 생성과 Exception 처리
try {
  //FileInputStream의 괄호 안에 읽어들일 파일의 경로 기재
  fis  = new FileInputStream("src/javaStudy2/part5/ByteExam1.java");
  //FileOutputStream의 괄호 안에 작성할 파일의 경로 기재
  fos = new FileOutputStream("byte.txt");
} catch (Exception e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}
  • 객체 생성할 때 괄호 안에 파일의 경로 작성 (경로를 적지 않으면 프로젝트 밑에 저장됨)
  • Exception 처리
    • FileNotFoundException(파일이 없는 경우 발생하는 Exception)을 처리하기 위해 try-catch문 사용
    • FileNotFoundException 외의 예외가 발생할 수 있기 때문에 catch문 안의 예외는 Exception으로 기재
    • 객체를 try문 밖에서도 사용할 수 있게 try문 밖에 선언 및 초기화

read( )메소드로 값을 읽어들이고 write( )메소드로 값을 적기

int readData = -1;

while((readData = fis.read()) != -1){
  fos.write(readData);
}  
  • 파일로부터 읽어들인 값을 담기 위해 readData라는 변수 선언 및 초기화
  • 읽어들인 값을 readData에 넣음
    • 읽어들일 값이 여러 줄일 수 있기 때문에 반복문 while 사용
    • FileInputStream로 읽어들인 값을 readData에 담아줄 거니까 readData = fis.read( )
    • 파일의 데이터를 다 읽을 때까지 수행시켜야 하니까 (readData = fis.read( ))의 값이 -1이 될 때까지 반복 (-1이 되면 반복문 종료)
  • 읽어들인 데이터인 readData를 FileOutputStream의 write( )메소드를 이용해 byte.txt에 적음

close( )메소드로 java.io의 모든 인스턴스화된 객체 닫기

}finally{
  try {
    fos.close();
  } catch (IOException e) {
    *// TODO Auto-generated catch block*e.printStackTrace();
    e.printStackTrace();
  }
  try {
    fis.close();
  } catch (IOException e) {
    *// TODO Auto-generated catch block*
    e.printStackTrace();
  }
}
  • java.io의 모든 객체들은 인스턴스화하고 나면 닫아줘야 함(close( )메소드)
  • 위에서 Exception이 발생해도 꼭 닫아줘야 하니까 finally문 안에 써줌
    • FileOutputStream 객체를 close( )메소드로 닫아줌
      • close( )메소드도 throws해서 IOException을 발생시키니까 try-catch문으로 Exception 처리해줘야 함
    • FileInputStream 객체도 close( )메소드로 닫아줌
      • close( )메소드도 throws해서 IOException을 발생시키니까 try-catch문으로 Exception 처리해줘야 함

 

실행결과

  • System.out.println( )을 사용하지 않았기 때문에 콘솔에 출력되는 건 없음

  • Package Explorer에서 프로젝트를 선택하고 새로고침하면 byte.txt 파일이 만들어진 걸 확인할 수 있음
    • 파일 안의 내용은 FileInputStream 객체에 적은 파일(ByteExam1)의 내용과 똑같음

 

FileInputStream의 read( )메소드

  • 1byte씩 읽을 수 있음
    • 정수 4byte 중 마지막 byte에 읽어들인 1byte를 저장
  • 리턴타입은 char가 아니라, int
    • read( )메소드가 char를 리턴한다면 끝을 나타내는 값(-1)을 표현할 수 없기 때문에, char가 아닌 int를 리턴
      • char 자료형은 2byte로 (0 ~ 2^16 - 1) 의 범위를 가짐
      • But, read( )메소드는 더 이상 읽을 문자가 없으면 -1을 리턴하기 때문에 실제로 필요한 범위는 (-1 ~ 2^16 - 1)
      • 이 범위를 만족하는 가장 작은 자료형이 int형이므로 리턴값의 크기가 int형(4byte)이 된 것
  • 음수의 경우 맨 좌측 비트가 1이 됨. 읽어들일 것이 있다면 항상 양수를 리턴한다고 볼 수 있음
  • 더 이상 읽어들일 것이 없을 때 -1을 리턴

 

실습

  • 문제 설명
    • data.txt의 내용을 그대로 읽어서 copy.txt로 복사하려고 합니다. 12번째 줄 부터 fis와 fos를 이용해서 코드를 완성해 보세요.
  • 해설
    • data.txt 파일에서 FileInputStream객체의 read( )메소드로 읽어들인 값을 readData에 저장하고, readData를 FileOutputStream객체의 write( )메소드로 copy.txt 파일에 작성하는 코드를 만들면 됨
  • ByteReadWriteExam 클래스
import java.io.*;

public class ByteReadWriteExam{
  public static void main(String[] args){
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try{
      fis = new FileInputStream("data.txt");
      fos = new FileOutputStream("copy.txt");
      //data.txt의 파일을 읽어서 그대로 copy.txt에 복사하려고 합니다.
      //이 아래에서 fis와 fos를 이용해서 파일 복사를 완성해 보세요.
      int readData = -1;
      while((readData = fis.read()) != -1){
        fos.write(readData);
      }
    }
    catch(Exception e){
      System.out.println(e);
    } 
    finally{
      //fis와 fos를 사용하고 나면 close해야 합니다.    
      try{
        fis.close();
        fos.close();
      }
      catch(Exception e){
        System.out.println(e);
      }
    }
  }
}


 

 Byte 단위 입출력 심화(File) 

Byte단위 입출력 클래스는 클래스의 이름이 InputStream이나 OutputStream으로 끝남

 

용량이 큰(512byte) Byte 단위 입출력 프로그램

  • 파일로부터 512byte씩 읽어들여 파일에 512byte씩 저장하는 프로그램을 작성
    • 1byte씩 읽어들이는 프로그램보다 속도가 빠름
  • 파일로부터 1byte씩 읽어들여 파일에 1byte씩 저장하는 코드를 재활용할 예정
  • 위의 코드에서 수정할 부분 
    • 512byte를 읽어들이기 위해 byte배열 buffer을 사용
    • 얼마나 빠른지 실행시간을 확인하기 위해 System.currentTimeMillis( )메소드 사용

 

완성 코드

  • 파일로부터 512byte씩 읽어들여, 파일에 512byte씩 저장하는 코드
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); 
  }
}

 

파일로부터 512byte씩 읽어들여, 파일에 512byte씩 저장하는 코드를 작성하는 과정

객체 선언 및 생성

  • FileInputStream: 파일로부터 읽어오기 위한 java.io의 클래스
  • FileOutputStream: 파일에 쓸 수 있게 해주는 java.io의 클래스
FileInputStream fis = null; 
FileOutputStream fos = null;        
try {
  fis = new FileInputStream("src/javaStudy2/part5/ByteExam1.java");
  fos = new FileOutputStream("byte.txt");
} catch (Exception e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}
  • 객체 생성할 때 괄호 안에 파일의 경로 작성 (경로를 적지 않으면 프로젝트 밑에 저장됨)
  • Exception 처리
    • FileNotFoundException(파일이 없는 경우 발생하는 Exception)을 처리하기 위해 try-catch문 사용
    • FileNotFoundException 외의 예외가 발생할 수 있기 때문에 catch문 안의 예외는 Exception으로 기재
    • 객체를 try문 밖에서도 사용할 수 있게 try문 밖에 선언 및 초기화

최대 512byte를 읽어들이기 위해 byte배열 buffer을 사용

int readCount = -1; 
byte[] buffer = new byte[512];
while((readCount = fis.read(buffer))!= -1){
  fos.write(buffer,0,readCount);
}
  • byte[] buffer = new byte[512];
    • 최대 512byte만큼 읽어들이기 위해 byte배열을 사용
    • byte배열의 크기는 읽어들일 수만큼인 최대 512byte로 설정
  • (readCount = fis.read(buffer))!= -1
    • FileInputStream 객체의 read( )메소드에 byte배열 buffer 삽입
  • 512byte를 읽어들이지 못하는 경우도 있음
    • 만약 파일의 크기가 1000byte라면 처음엔 512byte를 읽지만, 두 번째엔 488byte를 읽고, 마지막엔 읽어들일 값이 없기 때문에 read( )메소드는 -1을 반환할 것
  • 값을 저장할 변수로 readData라는 명칭 대신, 분명한 의미 전달을 위해 readCount로 수정
  • fos.write(buffer,0,readCount);
    • ‘byte배열 buffer를 0번째 값부터 readCount만큼 써주세요’라는 요청
      • 512byte만큼 읽어들였기 때문에 buffer를,
      • buffer의 0번째 인덱스부터 시작하니까 0을,
      • 읽어들이는 수를 readCount가 갖고 있기 때문에 readCount를 작성
  • while문 실행 과정 (파일의 크기가 1000byte라면)
    • 처음에 fis.read(buffer)가 512byte를 읽어 buffer[ ]에 입력하고 readCount에 512을 리턴 > fos.write(buffer,0,readCount)가 buffer[0]부터 buffer[511]까지 읽어서 출력
    • fis.read(buffer)가 나머지 488byte를 읽어 buffer[ ]에 입력하고 readCount에 488을 리턴 > fos.write(buffer,0,readCount)가 buffer[0]부터 buffer[487]까지 읽어서 출력
    • 그 다음에 fis.read(buffer)는 -1을 리턴하고, 반복문 종료

Sytem클래스의 currentTimeMillis( )메소드로 실행시간 확인

long startTime = System.currentTimeMillis(); 

.
.
.

long endTime = System.currentTimeMillis();
  *//메소드를 수행하는데 걸린 시간을 구할 수 있음*
System.out.println(endTime-startTime); 
}
  • System.currentTimeMillis( );
    • 현재 시간을 long타입으로 반환
  • currentTimeMillis( )메소드로 startTime과 endTime을 만듦
  • endTime - startTime: 프로그램 실행시간

 

실행 결과

1byte 프로그램과 512byte 프로그램의 실행시간 비교

  • 1byte 프로그램에도 System.currentTimeMillis( )메소드를 활용한 코드를 삽입
  • 1byte 프로그램과 512byte 프로그램의 실행시간을 각각 출력
  • 512byte 프로그램이 1byte 프로그램보다 빠름

 

1byte 프로그램과 512byte 프로그램의 실행시간이 속도 차이가 나는 이유

  • 파일을 1byte씩만 읽어오라고 해도 보통 운영체제는 512byte씩 읽어오니까 처음부터 배열의 크기를 512byte의 배수로 설정하는 게 성능상 좋음

  • 1byte를 두 번 읽어오라고 하면 512byte를 한 번 읽어와서 1byte만 쓰고 511byte는 버리고, 또 512byte를 한 번 읽어와서 1byte만 쓰고 511byte는 버림

 

read( )메소드의 오버로딩

  • read( )메소드와 read(byte[ ])메소드의 행동이 완전 다름
    • read(byte[ ])메소드는 read( )메소드를 오버라이딩한 것
  • read( )메소드와 read(byte[ ])메소드 비교
    • read( )메소드는 Byte 단위 입출력에서 사용한 메소드
    • read(byte[ ])메소드는 Byte 단위 입출력 심화에서 사용한 메소드 - read(buffer)
    • 공통점
      • 읽을 데이터가 없을 경우, -1을 리턴
    • 차이점
      • read( )메소드: 읽어온 바이트를 int로 리턴
      • read(byte[ ])메소드: 읽어온 byte는 byte[ ]에 저장하고 읽어온 값의 크기를 int로 리턴

 

 다양한 타입의 출력: try-with-resources 활용 

try-with-resources

  • close( )메소드를 사용하지 않아도, Exception이 발생하지 않았다면 사용한 자원을 자동으로 종료시켜주는 기능
  • java io객체는 인스턴스를 만들고, 모두 사용하면 close()메소드를 호출해야 하지만, try-with-resources를 사용하면 close( )메소드 쓸 필요 X
try(
    *//io객체 선언*
){
    *//io객체 사용*
}catch(Exception ex){
  ex.printStackTrace();
}

 

파일에 데이터를 쓰는 프로그램

  • data.txt 파일에 데이터를 쓸 것

 

완성 코드

  • 파일에 데이터를 쓰는 코드
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);
      out.writeBoolean(true);
      out.writeDouble(50.5);
    }catch (Exception e) {
      e.printStackTrace();
    }
  }   
}

 

파일에 데이터를 쓰는 코드를 작성하는 과정

try문의 소괄호( ) 안에 DataOutputStream 객체 선언

  • DataOutputStream는 어디에 데이터를 출력할지 결정할 수 없기 때문에 결정해줄 수 있는 다른 OutputStream을 넣어야 함
  • DataOutputStream 객체 out을 생성할 때 파일에 출력하게 하는 FileOutputStream를 넣어줌
  • 아무런 경로를 입력하지 않으면 프로젝트 바로 밑에 저장됨
DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));

try문의 중괄호 { } 안에서 DataOutputStream 객체 사용

  • DataOutputStream의 메소드: writeInt( )메소드, writeBoolean( )메소드, writeDouble( )메소드
try(
  DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));
){
  out.writeInt(100);
  out.writeBoolean(true);
  out.writeDouble(50.5);
}

catch블럭에서 Exception 처리

 try(
    DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));
){
  out.writeInt(100);
  out.writeBoolean(true);
  out.writeDouble(50.5);
}catch (Exception e) {
  e.printStackTrace();
}

 

실행 결과

  • 프로젝트를 새로고침하면 data.txt 파일이 생성된 걸 확인할 수 있음

  • 기본으로는 문자형이 쓰여지지만, 현재는 데이터 타입으로 사용했기 때문에 읽어낼 때 DataInputStream 클래스를 이용해야 함

 

DataOutputStream 클래스: 다양한 타입으로 데이터를 저장할 수 있는 클래스

  • DataOutputStream의 생성자는 추상클래스인 OutputStream을 매개변수로 받아들임
    • OutputStream의 자손이라면 무엇이든 받아들인다는 의미
    • DataOutputStream이 장식의 역할을 한다는 의미
      • 다양한 메소드를 제공한다는 뜻
      • But, 어디에 쓸지는 결정할 수 없기 때문에 결정하는 다른 장식대상 클래스(예: FileOutputStream)를 함께 써야 함
  • DataOutputStream의 메소드
    • DataOutputStream의 write( )메소드를 다양한 형식으로 오버로딩함
      • writeInt( )메소드: int 값으로 저장. int는 4byte를 차지
      • writeBoolean( )메소드: boolean 값으로 저장. boolean은 1byte를 차지
      • writeDouble( )메소드: double 값으로 저장. double은 8byte를 차지
      • 이외에도 데이터타입에 따라 메소드가 더 존재함

 

실습

  • 문제 설명
    • 다음 파일의 out을 이용해서 data.txt에 int값 100, double값 3.14를 순서대로 저장하세요.
  • 해설
    • out.writeInt( )메소드와 out.writeDouble( )메소드 사용
  • DataOutputStreamExam 클래스
import java.io.*;

public class DataOutputStreamExam{
  public static void main(String[] args){
        
    try(
        //try의 뒤에나오는 괄호()사이에서 만든 stream은 별도로 close하지 않아도 됩니다.
        DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));
    ){
      //이 아래에 out을 이용해서 data.txt에 int값 100, double값 3.14를 저장하세요.
      out.writeInt(100);
      out.writeDouble(3.14);
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }
}


 

 다양한 타입의 입력: try-with-resources 활용 

다양한 타입의 출력에서 만든 data.txt 파일 크기의 구성요소

  • 13byte = 4byte(int) + 1byte(boolean) + 8byte(double)

 

파일에 데이터를 읽어오는 프로그램

  • data.txt 라는 파일로부터 데이터를 읽어올 것

 

완성 코드

  • 파일에 데이터를 쓰는 코드
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"));
    ){
      int i = in.readInt();          
      boolean b = in.readBoolean();          
      double d = in.readDouble();

      System.out.println(i);
      System.out.println(b);
      System.out.println(d);
    }catch(Exception ex){
      ex.printStackTrace();
    }
  }
}

 

파일로부터 데이터를 읽어오는 코드를 작성하는 과정

try문의 소괄호( ) 안에 DataInputStream객체 선언

  • DataInputStream는 어디에 데이터를 출력할지 결정할 수 없기 때문에 결정해줄 수 있는 다른 InputStream을 넣어야 함
  • DataInputStream 객체 in을 생성할 때 파일에 출력하게 하는 FileInputStream를 넣어줌
  • 아무런 경로를 입력하지 않으면 프로젝트 바로 밑에 저장됨
DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));

try문의 중괄호 { } 안에서 DataInputStream 객체 사용

  • DataInputStream의 메소드: readInt( )메소드, readBoolean( )메소드, readDouble( )메소드
  • 메소드를 사용해 읽어온 값을 변수에 담음
try(
    DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));
){
  int i = in.readInt();          
  boolean b = in.readBoolean();          
  double d = in.readDouble();

  System.out.println(i);
  System.out.println(b);
  System.out.println(d);
}

catch블럭에서 Exception 처리

try(
    DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));
){
  int i = in.readInt();          
  boolean b = in.readBoolean();          
  double d = in.readDouble();

  System.out.println(i);
  System.out.println(b);
  System.out.println(d);
}catch(Exception ex){
  ex.printStackTrace();
}

 

실행 결과

  • data.txt 파일에 저장할 때 넣어둔 값들(100, true, 50.5)이 콘솔에 출력됨
    • 파일에 들어갔을 때 파일의 내용을 눈으로 볼 순 없지만, DataOutputStream을 이용해 데이터 타입 자체로 저장한 후에 DataInputStream을 이용해 해당 데이터타입으로 불러내서 사용할 수 있음

 

try-with-resources

  • close( )메소드를 사용하지 않아도, Exception이 발생하지 않았다면 사용한 자원을 자동으로 종료시켜주는 기능
  • java io객체는 인스턴스를 만들고, 모두 사용하면 close()메소드를 호출해야 하지만, try-with-resources를 사용하면 close( )메소드 쓸 필요 X
  • try문의 소괄호 ( )에서는 io 객체를 선언하고, 중괄호 { }에서는 io 객체를 사용

 

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

  • data.txt로부터 값을 읽어들여 화면에 출력하는 클래스
  • DataInputStream의 생성자는 추상클래스인 InputStream을 매개변수로 받아들임
    • InputStream의 자손이라면 무엇이든 받아들인다는 의미
    • DataInputStream이 장식의 역할을 한다는 의미
      • 다양한 메소드를 제공한다는 뜻
      • But, 어디에 쓸지는 결정할 수 없기 때문에 결정하는 다른 장식대상 클래스(예: FileInputStream)를 함께 써야 함
  • DataInputStream의 메소드
    • read( )메소드를 다양한 형식으로 오버로딩함
    • 아래의 메소드들이 호출되면 내부적으로 FileInputStream의 read( )메소드를 통해 파일로부터 읽어들여짐
      • readInt( )메소드: int 값을 읽어들이는 메소드
      • readBoolean( )메소드: boolean 값을 읽어들이는 메소드
      • readDouble( )메소드: double 값을 읽어들이는 메소드
      • 이외에도 데이터타입에 따라 메소드가 더 존재함
  • 파일에 저장된 순서대로 읽어들여야 함
    • 위의 예제에서 int, boolean, double 순서대로 저장했기 때문에 읽어들일 때도 같은 순서로 읽어들여야 함

 

실습

  • 문제 설명
    • data.txt에는 int형의 숫자가 3개 연속으로 들어있습니다. DataInputStream을 이용해 값을 읽어들인 다음 sum에 저장하세요.
  • 해설
    • try 구문의 소괄호 ( ) 안에 DataInputStream 객체를 선언
    • try 구문의 중괄호 { } 안에 int 값 3개를 저장, sum에 그 3개의 합 담기
  • DataInputStreamExam 클래스
import java.io.*;

public class DataInputStreamExam{
  public static int read3(){
    int sum = 0;
    //data.txt로부터 int값 3개를 읽어들여서 sum에 더하세요.
    try(
        DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));
    ){
      int a = in.readInt();
      int b = in.readInt();
      int c = in.readInt();
            
      sum = a + b + c;
            
    }catch(Exception e){
      e.printStackTrace();
    }
        
    //아래는 테스트를 위한 코드입니다. 수정하지 마세요.     
    return sum;
  }
}


 

 Char 단위 입출력(Console) 

char 단위 입출력 클래스는 클래스 이름이 Reader나 Writer로 끝남

 

키보드로부터 한 줄 입력받아서 콘솔에 출력하는 프로그램

  • System.in를 이용해 키보드로부터 데이터를 받아
  • BufferedReader를 이용해 한 줄 입력받기

 

완성 코드

  • char 단위 입출력 클래스를 이용해 키보드로부터 한 줄 입력받아서 콘솔에 출력하는 코드
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);
  }
}

 

키보드로부터 입력받아서 콘솔에 출력하는 코드를 작성하는 과정

InputStreamReader를 이용해 BufferedReader 객체 선언

  • 키보드로부터 한 줄씩 입력받기 위해서는 BufferedReader와 System.in이 필요
    • BufferedReader는 어디에서 데이터를 읽어올지 결정할 수 없기 때문에 결정해줄 수 있는 다른 Reader을 넣어야 한다는 뜻
    • System.in: 키보드로부터 입력받을 수 있음
    • BufferedReader: 한 줄씩 입력받을 수 있음
  • But, BufferedReader의 생성자엔 Reader 객체만 들어갈 수 있으며, System.in은 InputSteram 타입의 객체
    • 생성자에서 InputStream을 받아들일 수 있는 InputStreamReader가 필요
      • InputStreamReader은 System.in을 받아들일 수 있음
  • 결론: BufferedReader 객체를 생성할 때, BufferedReader의 생성자에 InputSteam 타입의 System.in을 Reader 타입으로 바꿔주는 InputStreamReader를 넣어주면 된다!
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

키보드로부터 한 줄씩 입력받을 수 있도록 String 변수를 설정하고 Exception 처리

  • BufferedReader의 readLine( )메소드는 Exception 처리를 해줘야 함
  • 키보드로부터 읽어온 값을 변수 line에 담아냄
    • try문 바깥에서도 사용할 수 있게 try문 밖에서 선언
String line = null;

try {
  line = br.readLine()
} catch (IOException e) {
  e.printStackTrace();
}

최종 출력 코드 작성

  • 변수 line을 출력하는 코드
    • 변수 line: BufferedReader의 readLine( )메소드가 수행될 때 한 줄 입력받아서 대입된 값
System.out.println(line);

 

실행 결과

  • 콘솔에 문장을 입력하고 Enter를 누르면 그대로 출력됨

 

키보드로부터 입력받아서 콘솔에 출력하는 코드의 다양한 활용법

  • 반복문을 이용해 여러 줄을 입력받게 할 수도 있고,
  • 데코레이터 패턴을 이용해 키보드 대신 File로부터 입력받게 할 수도 있고,
  • 입력받은 부분을 콘솔 대신 File이나 ArrayList 같은 자료구조로 저장할 수도 있음

 

System.in: 키보드를 의미 (InputStream)

  • java.lang 패키지 - System 클래스 - in 필드
  • System.in의 타입이 InputStream
    • BufferedReader의 생성자에 바로 들어갈 수 없음
    • nputStreamReader 클래스를 이용해야 함
  • System.in을 사용하면 키보드로부터 입력받을 수 있음

 

BufferedReader: 한 줄씩 입력받기 위한 클래스

  • java.io 패키지의 BufferedReader 클래스
    • 한 줄 입력받는 readLine( )메소드를 가짐
  • BufferedReader 클래스의 생성자
    • Reader 객체만 받아들일 수 있음
    • InputStream 타입의 객체를 입력받는 생성자가 없음
    • InputStream 타입의 객체인 System.in을 Reader 타입의 객체로 바꾸기 위해 java.io 패키지의 InputStreamReader을 이용해야 함

 

InputStreamReader: Reader 타입 클래스

  • java.io 패키지의 InputStreamReader 클래스
  • java.io 패키지의 Reader를 상속받기 때문에 Reader 타입임
  • 생성자에서 InputStream 타입을 받아들임
    • System.in을 받아들일 수 있음

 

데코레이터 패턴(Decorator Pattern)

  • 객체에 추가적인 요건(기능)을 동적으로 첨가하는 방식
  • 근원지가 될 수 있는 부분과 기능을 가지고 있는 부분을 끼워서 사용
    • 근원지: 키보드로부터 값을 입력받는 System.in
    • 기능: 한 줄씩 입력받는 BufferedReader
  • 서브클래스를 만드는 것을 통해 기능을 유연하게 확장할 수 있는 방법을 제공
    • 서브클래스 예시: InputStreamReader(System.in)
  • BufferedReader의 생성자에 InputSteam 타입의 System.in을 Reader 타입으로 바꿔주는 InputStreamReader를 넣어주는 것도 데코레이터 패턴의 일종

 

 Char 단위 입출력(File) 

char단위 입출력 클래스는 클래스 이름이 Reader나 Writer로 끝남

 

파일에서 한 줄씩 입력받아서 파일에 출력하는 프로그램

  • 파일에서 읽기 위해 FileReader 클래스 이용
  • 한 줄 읽어들이기 위해서 BufferedReader 클래스 이용
  • 파일에 쓰게 하기 위해 FileWriter 클래스 이용
  • 편리하게 출력하기 위해 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/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){
      e.printStackTrace();
    }finally {
      pw.close();
      try {
        br.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

 

파일에서 한 줄씩 입력받아서 파일에 출력하는 코드를 작성하는 과정

FileReader를 이용해 BufferedReader 객체 생성

  +  FileWriter를 이용해 PrintWriter 객체 생성

  +  FileReader를 Exception 처리

  • FileReader에서 FileNotFoundException이 발생할 수 있기 때문에 Exception 처리를 해줘야 함
    • 다른 메소드를 사용할 때 FileNotFoundException 외의 Exception이 발생할 수 있기 때문에 catch문 소괄호( )안의 Exception 타입을 Exception을 처리
  • try문 안에서 객체를 선언하면 try문 밖에서 못 쓰니까 try문 밖에서 선언 및 초기화
  • 요즘은 PrintWriter가 발전해서 PrintWriter 생성자 자체가 파일을 받아들임
    • FileWriter를 쓰지 않아도 된다는 뜻
    • PrintWriter pw = new PrintWriter(”test.txt”); 로도 작성 가능
BufferedReader br = null; 
PrintWriter pw = null;
try{        
  br = new BufferedReader(new FileReader("src/javaStudy2/part5/CharIOExam2.java"));
  pw = new PrintWriter(new FileWriter("test.txt"));
}catch(Exception e){
  e.printStackTrace();
}

파일로부터 한 줄씩 입력받을 수 있도록 String 변수를 설정

  +  입력받은 값을 파일에 저장하는 반복문 작성

  • 한 줄 읽었을 때 그 값을 저장할 수 있는 변수 line 선언
  • 여러 줄을 받기 위해 while문 사용
    • 파일로부터 한 줄 입력받은 값을 line에 대입하고, 그 값이 null이 될 때까지 반복문 실행
      • BufferedReader의 readLine( )메소드는 더 이상 읽어들일 내용이 없을 때 null을 리턴
  • pw.println(line);
    • 읽어들인 값을 파일에 작성
String line = null;
while((line = br.readLine())!= null){
  pw.println(line);
}

close( )메소드로 java.io의 모든 인스턴스화된 객체 닫기

  • PrintWriter 클래스의 close( )메소드는 Exception 처리를 안 해도 되고, BufferedReader의 close( )메소드는 Exception 처리를 꼭 해야 함
  • br.close( )만 Exception 처리를 하는 이유
    • PrintWriter 클래스가 정의하는 close( )메소드
      • public void close( ) 으로 정의되어 있음
      • Exception을 throws하고 있지 않으므로 UnCheckedException
      • 메소드를 사용하는 쪽에서 Exception을 처리할 필요 X
    • BufferedReader 클래스의 close( ) 메소드
      • public void close( ) throws IOException 으로 정의되어 있음
      • 메소드 안에서 발생할 수 있는 IOException을 throws하고 있으므로 CheckedException
      • 그 메소드를 사용하는 쪽에서는 반드시 처리할 수밖에 없음 > 해당 예제에선 try-catch 블럭을 이용해서 Exception을 처리한 것
    • Exception을 처리하는 두 가지 방법
      • try-catch 블럭을 이용해서 직접 처리 또는 throws 시키기
      • throws: 예외가 발생했을 때 예외를 호출한 쪽에서 처리하도록 던져줌(떠넘김) > exception을 처리하지 않았다는 의미이기도 함
      • CheckedException은 반드시 처리해야만 하니까 throws했을 땐 반드시 해당 메소드를 사용하는 쪽에서 처리해야지만 에러가 발생하지 않음
}finally {
  pw.close();
  try {
    br.close();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

 

실행 결과

  • System.out.println( )을 사용하지 않았기 때문에 콘솔에 출력되는 건 없음

  • Package Explorer에서 프로젝트를 선택하고 새로고침하면 test.txt 파일이 만들어진 걸 확인할 수 있음

  • test.txt와 CharIOExam2.java 파일의 내용이 완전히 일치함

 

BufferedReader: 한 줄씩 입력받기 위한 클래스

  • java.io 패키지의 BufferedReader 클래스
  • BufferedReader는 장식하는 클래스라 어디에 쓸지는 결정할 수 없기 때문에 결정하는 다른 장식대상 클래스인 FileReader를 함께 써야 함
  • 한 줄 입력받는 readLine( )메소드를 가짐
    • readLine( )메소드는 더 이상 읽어들일 내용이 없을 때 null을 리턴

 

FileReader: 파일에서 읽어오는 클래스

  • java.io 패키지의 FileReader 클래스
  • java.io 패키지의 Reader를 상속받기 때문에 Reader 타입임

 

PrintWriter: 편리하게 출력할 수 있게 하는 클래스

  • java.io 패키지의 PrintWriter 클래스
  • PrintWriter는 장식하는 클래스라 어디에 쓸지는 결정할 수 없기 때문에 결정하는 다른 장식대상 클래스인 FileWriter를 함께 써야 함
  • But, 요즘은 PrintWriter가 발전해서 PrintWriter 생성자 자체가 파일을 받아들임
    • FileWriter를 쓰지 않아도 됨
    • 예: PrintWriter pw = new PrintWriter(”test.txt”);
  • println( )메소드를 가짐
  • System.out.println( )에서 System.out의 타입이 PrintWriter

 

FileWriter: 파일에 쓰는 클래스

  • java.io 패키지의 FileWriter 클래스
  • java.io 패키지의 Writer를 상속받기 때문에 Writer 타입임

 

실습

  • 문제 설명
    • PrintWriter를 통해 data.txt파일에 "안녕하세요. PrintWriter입니다."라고 적어보세요.
  • 해설
    • FileNotFoundException 등의 Exception을 처리하기 위해 try-catch-finally문 사용
    • FileWriter 클래스를 사용해 PrintWriter 객체 선언 및 생성
      • try 밖에서도 객체를 사용할 수 있게 try문 밖에서 객체를 선언 및 초기화
    • PrintWriter의 println( )메소드를 이용해 문제의 값 담기
    • catch문으로 Exception 처리
    • finally문으로 객체 닫기
  • CharIOExam 클래스
import java.io.*;

public class CharIOExam{
  public static void main(String[]args){
    PrintWriter pw = null;
    try{
      pw = new PrintWriter(new FileWriter("data.txt"));
      pw.println("안녕하세요. PrintWriter입니다.");
    } catch(Exception e){
      e.printStackTrace();
    } finally {
      pw.close();
    }
  }
}