본문 바로가기
Programming/Java

[자바/Java] 파일 I/O - File 입출력

by 코딩하는 랄로 2023. 10. 18.
728x90

파일 입출력(I/O) 

키보드에서 값을 입력(Scanner) 받고, 콘솔 창에 값을 출력(System.out.println)하는 과정은 익숙하다. 그렇다면 파일에서 값을 읽어오고(입력) 저장(출력)하려면 어떻게 해야 할까?

 

파일 입출력, Scanner 등은 모두 입출력이라는 범위 안에 존재하고 자바에서는 입출력을 수행하기 위해 스트림이라는 개념을 사용한다. 스트림(Stream)이란, 두 노드(키보드, 모니터, 메모리, 파일 등) 사이를 연결하는 데이터가 지나가는 통로이다.

 

스트림은 단방향 통신만이 가능하기 때문에 하나의 스트림으로 입출력을 같이 처리할 수 없다. 그렇기 때문에 입력 스트림, 출력 스트림 두 개의 스트림이 반드시 필요하다.

 

또한 스트림은 처리하는 데이터의 타입에 따라 아래와 같이 나뉜다.

이름 데이터 타입 클래스명 형식
바이트 기반 스트림 바이트 단위(8bit) XXXStream
캐릭터 기반 스트림 캐릭터 XXXer

 

 

 

바이트 기반 스트림

바이트 기반 스트림의 모든 클래스는 입력에 관한 클래스는 InputStream을 출력에 관한 클래스는 OutputStream을 조상으로 갖는다. 파일 입출력을 위한 FileInputStream/FileOutputStream도 두 클래스를 상속받고 있다.

 

InputStream

InputStream은 추상클래스로 read 메서드 등의 메서드가 있고 아래는 자주 사용하는 메소드이다.

public abstract class InputStream extends Object implements Closeable{
   
    // 자식 클래스들이 구현해야할 read 추상 메서드  
    // 바이트 하나를 읽어서 int로 반환하되, 더 이상 읽을 값이 없으면 -1을 리턴.
    public abstract int read() throws IOException;
   
    // len 바이트의 데이터를 읽어서 배열 b에 off 위치부터 집어넣기 (off위치는 배열 b의 index라고 생각하면 됨)
    // 읽은 바이트 개수를 반환하되, 더이상 읽을 값이 없으면 -1을 리턴
    public int read(byte[] b, int off, int len){
    	...
    }
   
    //byte b의 길이만큼 데이터를 InputStream으로부터 읽어들여 byte 배열 b에 삽입.
    //읽은 바이트 개수를 반환하되, 더이상 읽을 값이 없으면 -1을 리턴
    public int read(byte[] b) throws IOException {
    	...
       
    }
    // InputStream을 닫는역할.
    public void close() throws IOException{
    	...
    }
    ...

}

 

FileInputStream

FileInputStream은 위의 InputStream을 상속 받아 파일에 값을 읽어오도록 InputStream의 여러 메소드를 오버라이딩한 클래스이다.

public class FileInputStream extends InputStream{
	
    //생성자 목록
    public FileInputStream(File file){
		...    
    }
    public FileInputStream(String name){
    	...
    }
    ...

    //메서드
    public int read(){
    	...
    }
    public int read(byte[] b){
    	...
    }
    public int read(byte[] b, int off, int len){
    	..
    }
    ...


}

 

이를 이용하여 파일을 읽어오는 예제 코드를 작성해보겠다.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class InputStreamtest {
	public static void main(String[] args) {
        try {
            FileInputStream fin1 = new FileInputStream("./test1.txt");
            /* 
            1.  txt 파일 위치
            - 절대 경로 : 해당 파일의 모든 경로 ex) C:\user\...
            - 상대 경로 : 해당 자바 파일에서의 경로
                          ./ : 현재 디렉토리
                          ../ : 상위 디렉토리 이동
                          / : 하위 디렉토리 이동
            2. 다형성의 성질을 이용해 
            FileInputStream fin1 = new FileInputStream("./test1.txt"); 대신
            InputStream fin1 = new FileInputStream("./test1.txt"); 으로 사용할수도 있음
    
            3. 생성자로 String 타입 대신 File타입을 사용할수도 있음.
            */
  
	    int data;
	    while((data = fin1.read()) != -1)	//파일 다 읽으면 read()는 -1반환한다.
		System.out.print((char)data);

	    } catch (IOException e) {
		e.printStackTrace();
	    } finally { //stream close!!
            try {
                if(fin1 != null) fin1.close();
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

	}

}

 

위의 코드를 통해 파일을 읽어올 경우, 영어는 잘 읽히지만 한글은 깨지는 것을 알 수 있을 것이다. 그 이유로는 바이트 기반의 입력 스트림이기 때문이다.

 

1byte는 0 ~ 255까지의 범위를 가지고, 이는 아스키 값들이 여기에 모두 들어간다. 하지만 한글은 2byte가 필요한 문자이기 때문에 바이트 기반인 스트림에서는 깨지는 것이다.

 

한글을 깨지기 않게 하기위해서는 캐릭터 기반 스트림을 사용해도 되지만  FileInputStream의 byte[]을 넘겨받는 read 메소드를 사용하면 바이트 기반을 이용해도 깨지지 않게 할 수 있다.

 

 

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class InputStreamtest {
	public static void main(String[] args) {
        try {
            FileInputStream fin1 = new FileInputStream("./test1.txt");
            
            byte[] buffer = new byte[1024];
            int lengthRead = 0; //읽은 바이트 개수
            while(true) {	
                //매개변수로 주어진 byte[]의 길이만큼 read
                //실제 읽어들인 데이터는 매개변수 byte[]에 담김
                //읽어들인 바이트 개수 리턴, 읽어들인게 없으면 -1리턴
                lengthRead = fin1.read(buff);
                if(lengthRead == -1) break;
                
                System.out.print(new String(buff));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
	    } catch (IOException e) {
            e.printStackTrace();
	    } finally { //stream close!!
            try {
                if(fin1 != null) fin1.close();
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
	}
}​

 

OutputStream

OutputStream은 추상클래스로 write 메서드 등의 메서드가 있고 아래는 자주 사용하는 메소드이다.

public abstract class OutputStream extends Object implements Closeable, Flushable{

	//자식들이 구현해야할 write(int b) 추상 메서드
	// 주어진 값 b를 노드에 write.
	public abstract void write(int b) throws IOException;
   
	//배열 b에 있는 데이터 전부를 노드에 write.
	public void write(byte[] b) throws IOException {
    	...
   	}
   
	// 바이트 배열 b에 저장된 데이터 중 off위치부터 len개를 읽어서 노드에 write
   	public void write(byte[] b, int off, int len) throws IOException {
    	...
   	}
	
   	// 출력 스트림에 있는 모든 데이터를 노드에 출력하고 버퍼를 비운다.
	public void flush() throws IOException {
    	...
	}
   
   	// OutputStream을 닫는 역할
	public void close() throws IOException{
    	...
    }
	...
}

 

FileOutputStream

FileInputStream처럼 매개변수로 String으로도 사용가능하며 File 타입으로도 사용가능하다. 이번에는 한글을 깨지지 않도록 파일을 읽어와서 파일에 저장해보겠다.

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

public class InputStreamtest {
	public static void main(String[] args) {
        try {
            FileInputStream fin = new FileInputStream("./test1.txt");
            FileOutputStream fout = new FileOutputStream("./test2.txt");
            
            byte[] buffer = new byte[1024];
            int lengthRead = 0; //읽은 바이트 개수
            while(true) {	
                lengthRead = fin1.read(buff);
                if(lengthRead == -1) break;
                
                fout.write(buff, 0, lenthRead);
            }
            
            /*
            byte단위로 읽어오기
            int data;
            while((data = fin.read()) != -1)
                fout.write(data);
            */

        } catch (FileNotFoundException e) {
            e.printStackTrace();
	    } catch (IOException e) {
            e.printStackTrace();
	    } finally { //stream close!!
            try {
                if(fin != null) fin.close();
                if(fout != null) fout.close();
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
	}
}​

 

FileInputStream, FileOutputStream을 사용하는 방법은 기본적으로 한 바이트씩 입,출력 하는 것이기 때문에 기본적으로 시간이 오래 걸린다.

 

이러한 시간은 버퍼를 이용하면 속도를 대폭 줄일 수 있는 데, 그 역할을 해주는 것이 BufferedInputStream, BufferedOutputStrem이다.

 

BufferedInputStream

public class BufferedInputStream extends FilterInputStream{

  // 생성자 
  // 주어진 InputStream 객체를 매개변수로 받으며 버퍼의 크기를 지정해주지 않았으므로
  // 기본적으로 8192byte 크기의 버퍼를 가짐.
  public BufferedInputStream(InputStream in){
  ...
  }
  // 주어진 InputStream 객체를 매개변수로 받으며 지정된 size크기의 버퍼를 갖는
  // BufferedInputStream객체를 생성한다.
  public BufferedInputStream(InputStream in, int size){
  ...
  }

  //메서드 (InputStream과 하는 역할 동일)
  public int read() throws IOException{
   ...
  }
  //(InputStream과 하는 역할 동일)
  public int read(byte[] b, int off, int len) throws IOException{
   ...
  }
   ...

}

 

 BufferedOutputStream

public class BufferedOutputStream extends FilterOutputStream{

    // 생성자 
    // OutputStream 객체를 매개변수로 받으며 버퍼의 크기를 지정해주지 않으면
    // 8192 byte 크기의 버퍼를 갖게 된다.
	public BufferedOutputStream(OutputStream out){
    	...
    }
    // OutputStream 객체를 매개변수으로 받으며 지정된 size크기의 버퍼를 갖는
    // BufferedOutputStream 객체를 생성한다.
    public BufferedOutputStream(OutputStream out, int size){
   		...
    }
   
    //메서드 (OutputStream과 하는 역할 동일)
	public void write(int b) throws IOException{
    	...
    }
    // (OutputStream과 하는 역할 동일)
    public void write(byte[] b, int off, int len) throws IOException{
    	...
    }
    //버퍼의 모든 내용을 출력한후 버퍼 비움
    public void flush() throws IOException{
    	...
    }
	...    

}

 

Buffered를 이용하여 파일 입출력 예제를 작성해보겠다.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Bufferedstreamtest {

	public static void main(String[] args) {
		try {
			FileInputStream fin = new FileInputStream("./test3.txt");
			BufferedInputStream bin = new BufferedInputStream(fin);
		
			FileOutputStream fout = new FileOutputStream("./test4.txt");
			BufferedOutputStream bout = new BufferedOutputStream(fout);
		
			int data;
			while((data =bin.read()) != -1) {
				bout.write(data);
			}
		
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
            try {
                if(fin != null) fin.close();
                if(fin != null) fin.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
	}
}

 

 

캐릭터 기반 스트림

캐릭터 기반 스트림도 바이트 기반 스트림과 마찬가지고 각 클래스들은 기본적으로 입력 스트림은 Reader를, 출력 스트림은 Writer를 상속 받는다.

 

Reader

public abstract class Reader extends Object implements Readable, Closeable{
    // 주요 메서드
    // 캐릭터 하나를 읽는다. char 범위인 0~65535범위의 읽은 정수를 반환하며
    // 입력스트림 마지막에 도달하면 -1을 반환한다.
    public int read() throws IOException {
    	...
    }
    // 입력소스로부터 매개변수로 주어진 배열 c의 크기만큼을 읽어서 배열 cbuf에 저장
    // 반환값은 읽어온 데이터의 개수이다. 
    // 입력스트림의 마지막에 도달하면 -1을 반환한다.
    public int read(char[] cbuf) throws IOException{
    	...
    }
    // 입력소스로부터 len개의 문자를 읽어서 배열 cbuf의 지정된 위치(off)로 부터 읽은만큼 저장한다. 
    // 읽어들인 데이터의 개수를 반환하되, 입력스트림 마지막에 도달시 -1을 반환한다.
    public abstract int read(char[] cbuf, int off, int len) throws IOException{
		...    
    }
    // 입력스트림을 닫음으로써 사용하고 있던 자원을 반환한다.
    public abstract void close() throws IOException {
    	...
    }
	...
}

 

Writer

public abstract class Writer extends Object implements Appendable, Closeable, Flushable{
	// 주요 메서드
    // 지정된 문자 c를 출력소스에 출력한다.
    public Writer append(char c) throws IOException{
    	...
    }
    // 지정된 문자열 csq를 출력소스에 출력한다. 
    public Writer append(CharSequence csq) throws IOException{
    	...
    }
    // 지정된 문자열 csq중 일부(start <= < end)를 출력소스에 출력한다. 
    public Writer append(CharSequence csq, int start, int end) throws IOException{
    	...
    }
    // 출력스트림을 닫는다.
    public abstract void close() throws IOException{
    	...
	}
    // 스트림의 버퍼에 있는 모든 내용을 출력소스에 쓴다. (버퍼가 있는 스트림에만 해당)
    public abstract void flush() throws IOException{
    	...
    }
    // 주어진 문자열(str)을 출력소스에 쓴다.
    public void write(String str) throws IOException{
    	...
    }
    // 주어진 값(c)를 출력소스에 쓴다.
    public void write(int c) throws IOException{
    	...
    }
    // 주어진 배열 cbuf에 저장된 모든 내용을 출력소스에 쓴다.
    public void write(char[] cbuf) throws IOException{
    	...
    }
    // 주어진 배열 cbuf에 저장된 내용 중에서 off번째부터 len 길이만큼만 출력소스에 쓴다.
    public abstract void write(char[] cbuf, int off, nt len) hrows IOException{
    	...
    }
    // 주어진 문자열 str의 off번째 문자부터 len개만큼의 문자열을 출력소스에 쓴다.
    public void write(String str, int off, int len) throws IOException{
    	...
    }
    ...
}

 

FileReader, FileWriter도 위의 두 클래스를 상속받은 클래스이다. 이를 통한 예제를 작성해보겠다.

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileReaderWriterTest {

	public static void main(String[] args) {
        //try-catch-resource문
        //try() 안에 있는 객체에 대하여 자동으로 close를 호출해줌
        try(
            FileReader fin = new FileReader("./test1.txt"); 
            FileWriter fout = new FileWriter("./test2.txt");
        ) {
            int data;
            while((data = fin.read()) != -1){
                System.out.print((char)data);
                fout.write(data);
            }
			
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}

 

FileReader와 FileWriter는 바이트 기반 스트림과는 달리, 한글이 깨지지 않는다!!

 

FileReader와 FileWriter 또한 buffer를 사용한 BufferedReader, BufferedWriter를 사용하면 보다 빨리 입출력이 가능하다.

 

BufferedReader

public class BufferedReader extends Reader{
	// 생성자
    public BufferedReader(Reader in, int sz){
    	...
    }
    public BufferedReader(Reader in){
    	...
    }
    // 메서드 
    // 한줄을 읽어들여 String으로 반환한다. 
    // 스트림의 끝에 도달시 null을 반환.
    public String readLine() throws IOException{
    	...
    }
    ...
}

 

BufferedWriter

public class BufferedWriter extends Writer{
	// 생성자
    BufferedWriter(Writer out){
    	...
    }
    BufferedWriter(Writer out, int sz){
    	...
    }
   	// 메서드
    // 줄바꿈 문자를 쓴다.
    void newLine(){
    	...
    }
    ...
}

 

이를 이용하여 코드를 작성하면 다음과 같이 작성할 수 있다.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedReaderWriterTest {

	public static void main(String[] args) {
        //try-catch-resource문
        //try() 안에 있는 객체에 대하여 자동으로 close를 호출해줌
        try(
            BufferedReader bin = new BufferedReader(new FileReader("./test1.txt")); 
            BufferedWriter bout = new BufferedWriter(new FileWriter("./test2.txt"));
        ) {
            String line="";
            while((line = bin.readLine()) != null){
                bout.write(line);
                bout.newLine();
            }
			
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}
728x90